Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

preact 源码学习系列之二:组件的渲染与更新 #104

Open
youngwind opened this issue Jun 5, 2017 · 1 comment
Open

preact 源码学习系列之二:组件的渲染与更新 #104

youngwind opened this issue Jun 5, 2017 · 1 comment

Comments

@youngwind
Copy link
Owner

youngwind commented Jun 5, 2017

在上一篇 #103 中,我们已经掌握了“如何解析渲染一段 JSX 结构”。今天,我们进一步研究:如何解析、渲染和更新组件。

变量的解析

在研究组件解析之前,我们先来看个更简单的例子。

import {h, render, Component} from '../../preact';

class Person extends Component {
    constructor() {
        super();
        this.state = {
            name: "youngwind"
        }
    }

    render() {
        return (
            <div>
                {this.state.name}
            </div>
        )
    }
}

render(<Person />, document.body);

问题:在渲染 Person 组件的时候,如何将 this.state.name 解析成真实值 "youngwind" 呢?是用正则匹配替换吗?
答案:不需要处理,因为 babel 已经帮我们处理好了。
请看编译后的代码。

变量的解析
由图中我们可以看出:由于 this.state.name被当做函数参数传递给 h 函数,因此 h 函数在执行的时候会对其进行自动求值。也就是说,对于变量的解析,我们并不需要做任何特殊处理。

组件标签的解析

再看一个更复杂的例子。

import {h, render, Component} from '../../preact';

class Person extends Component {
    constructor() {
        super();
        this.state = {
            name: "youngwind",
            age: 25
        }
    }

    render() {
        return (
            <div>
                <Name name={this.state.name}/>
                <Age age={this.state.age}/>
            </div>
        )
    }
}

class Name extends Component {
    render(props) {
        return (
            <div>
                {props.name}
            </div>
        )
    }
}

class Age extends Component {
    render(props) {
        return (
            <div>
                {props.name}
            </div>
        )
    }
}

render(<Person />, document.body);

编译后的代码如下:

组件的解析
由图中我们可以发现:babel 进行 JSX 解析时,对普通标签和组件标签的处理是类似的,只不过有两点不同。

  1. 对于组件来说,h 函数的第一个参数不是标签字符串,而是组件类;
  2. 所谓传递给组件的 props,其实就相当于给普通标签定义 attributes。

组件的渲染与更新

有了上面的基础,我们来看看如何实现组件的渲染和更新,举个例子。

import {h, render, Component} from '../../preact';

class Person extends Component {
    constructor() {
        super();
        this.state = {
            name: "youngwind",
            age: 25
        }
    }

    change() {
        let {name, age} = this.state;
        this.setState({
            name: name + '啦',
            age: age + 1
        });
    }

    render() {
        return (
            <div>
                <button onclick={this.change.bind(this)}>改变</button>
                <Name name={this.state.name}/>
                <Age age={this.state.age}/>
            </div>
        )
    }
}

class Name extends Component {
    render(props) {
        return (
            <div>
                <label>姓名:</label>
                <span>{props.name}</span>
            </div>
        )
    }
}

class Age extends Component {
    render(props) {
        return (
            <div>
                <label>年龄:</label>
                <span>{props.age}</span>
            </div>
        )
    }
}

render(<Person />, document.body);

这段代码,我们期望的功能是:

  1. 通过 props 给 Name 和 Age 传递参数,初次渲染的时候分别显示 "youngwind" 和 "25";
  2. 点击“改变”按钮,调用 setState,触发 Name 和 Age 的重新渲染,显示 "youngwind啦" 和 "26"。

具体的实现逻辑较为繁复,难以用文字描述清楚,我画了个流程图,可以对比着我实现的代码看。
preact组件渲染与更新逻辑图

效果

最终实现的效果如下所示。(注意,效果图中控制台会输出一些生命周期的信息,这部分的代码我在 demo 中省略了,完整的代码请参考这里
demo

后话

虽然在本文中已经实现了组件的更新,但是,并没有应用虚拟 DOM 的 diff 算法,之后有时间再去研究研究。

@BetaSu
Copy link

BetaSu commented Aug 3, 2018

感谢博主分享的文章,真是简洁明了。我照着您的源码重新实现,在过程中遇到一个疑问,想请教博主一下:
在源码 91~96行, build函数中

// 判断新子节点是否已经存在原有 DOM 中
newChildren.forEach((newChild, i) => {
    if (children[i] !== newChild) {
        out.appendChild(newChild);
    }
});

这里为什么要使用appendChild,不应该直接用 newChildren 替换掉 children么。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants