接上文,
React流程图:
https://bogdan-lyashenko.gith…
最后的最后
更新方法基于子元素上的多个属性去处理子元素。这里会有几种场景,但是技术上来说主要是两种。一种是子元素仍然是‘复杂’对象,也就是说子元素还是React组件,React需要递归处理嵌套的子组件直到到达他们的内容层级。还有一种,就是子元素已经是内容层级里,内容就是字符串或者数字或者其他简单类型。
处理方式是根据nextProps.children的类型来判断的。在我们的列子中,组件
<ExampleApplication>组件有三个子元素,button,childCmp 和 text string。
我们看下它是如何运作的。
在Examplication子元素的第一次迭代期间,子元素的类型不是内容,所以需要进入到‘复杂’组件的处理逻辑。我们遍历所有的子元素,按之前处理它们父元素的过了处理它们。顺便提下,验证shouldUpdateReactComponent的代码块会令人有点疑惑,表面上看起来这个是用来检测是否需要更新,但是实际上,它除了检测更新,还检测删除,新建操作(为了简化流程图,相应的代码没有展示在流程图中)。之后,我们将旧元素和当前元素,如果一些子元素被移除了,则我们需要卸载对应的组件并同时移除它。
接下去,在第二次迭代过程中,我们需要处理button,这个相对来说比较简单,因为button的子元素就是文本,内容就是‘set state button’。我们检测下之前的内容是否跟现在的保持一致,嗯,文本没有发生改变,所以我们不需要更新button。逻辑上来说这样很正确,其实这就是虚拟DOM的作用,现在虚拟DOM听起来就具体了些,是不是?React会维护内部DOM,同时只在需要时才去处理真正的DOM节点,通过这种方式,很自然的会提高性能。到这里,你应该已经能理解React的设计思想了,这之后,我们对ChildCmp进行更新,它的子元素会被遍历直到它的内容层级并进行更新。在我们的列子里,通过click和setState的调用,’click state message’会对this.props.message进行更新。
//...
onClickHandler() {
this.setState({ message: 'click state message' });
}
render() {
return <div>
<button onClick={this.onClickHandler.bind(this)}>set state button</button>
<ChildCmp childMessage={this.state.message} />
//...
现在我们要对元素内容进行更新,事实上,是替换它的内容。那么是如何进行更新的呢?一个类似拥有配置信息的配置对象会被解析,并且配置对象里的定义的动作会被执行。对应我们的例子,文本更新的配置会像如下:
{
afterNode: null,
content: "click state message",
fromIndex: null,
fromNode: null,
toIndex: null,
type: "TEXT_CONTENT"
}
正如你所见,它几乎就是一个空对象,这个文本内容更新例子是相当直白的。配置对象里有很多字段,这是因为在对DOM节点进行移动时,配置对象会比文本更新的配置对象相对来说会更复杂些:
看下源码,这应该会让我们有个更清晰的认识:
//src\renderers\dom\client\utils\DOMChildrenOperations.js#172
processUpdates: function(parentNode, updates) {
for (var k = 0; k < updates.length; k++) {
var update = updates[k];
switch (update.type) {
case 'INSERT_MARKUP':
insertLazyTreeChildAt(
parentNode,
update.content,
getNodeAfter(parentNode, update.afterNode)
);
break;
case 'MOVE_EXISTING':
moveChild(
parentNode,
update.fromNode,
getNodeAfter(parentNode, update.afterNode)
);
break;
case 'SET_MARKUP':
setInnerHTML(
parentNode,
update.content
);
break;
case 'TEXT_CONTENT':
setTextContent(
parentNode,
update.content
);
break;
case 'REMOVE_NODE':
removeChild(parentNode, update.fromNode);
break;
}
}
}
我们的实例会走到’TEXT_CONTENT’的分支里,然后这就是最后一步了,React调用setTextContent方法,此方法会对真正的DOM节点进行内容更改。
不错不错,最终内部被更新到了页面上,这就是一个重绘的过程了。还有什么东西没有讲到吗?嗯,不要着急,让我们先完成这个更新过程。所有的东西都已经准备完毕,所有我们组件的componentDidUpdate方法会被调用。那么,这种延迟调用是如何实现的呢?没错,就是使用事务包装器。就像之前提到的,‘脏’组件的更新是被ReactUpdateFlushtransaction给包装过的,其中一个包装器里就有调用this.callbackQueue.notifyAll的逻辑,也就是在这里,componentDidUpdate会被调用。完美!
现在,我们真的完成了整个过程。