14.0 第 14 部分(点击查看大图)
在发起子组件更新操作时会有很多属性影响子组件内容。这里有几种可能的情况,不过其实就只有两大主要情况。即子组件是不是 “复杂”。这里的复杂的含义是,它们是React组件,React应当通过它们不断递归直到触及内容层,或者,该子组件只是简单数据类型,比如字符串、数字。
这个判断条件就是步骤 (1) 的 nextProps.children
的类型,在我们的情形中,ExampleApplication
有三个孩子 button
, ChildCmp
和 text string
。
很好,现在让我们来看它的工作原理。
首先,在首次迭代时,我们分析 ExampleApplication children
。很明显可以看出子组件的类型不是 “纯内容类型”,因此情况为 “复杂” 情况。然后我们一层层往下递归,每层都会判断 children 的类型。顺便说一下,步骤 (2) 的 shouldUpdateReactComponent
判断条件可能让你有些困惑,它看上去是在验证更新与否,但实际上它会检查类型是更新还是删除与创建(为了简化流程我们跳过此条件为否的情形,假定是更新)。当然接下来我们对比新旧子组件,如果有孩子被移除,我们也会去除挂载组件,并把它移除。
14.1 Children 更新 (点击查看大图)
在第二轮迭代时,我们分析 button
,这是一个很简单的案例,由于它仅包含一个标题文字 set state button
,它的孩子只是一个字符串。因此我们对比一下之前和现在的内容。很好,这些文字并没有变化,因此我们不需要更新 button
?这非常的合理,因此所谓的 “虚拟 DOM”,现在听上去也不是那么的抽象,React 维护了一个对 DOM 的内部表达对象,并且在需要的时候更改真实 DOM,这样取得了很不错的性能。因此我想你应该已经了解了这个设计模式。那我们接着来更新 ChildCmp
,然后它的孩子也到达我们可以更新的最底层。可以看到在这层的内容已经被修改了,当时我们通过 click
和 setState
的调用,this.props.message
已经更新成 'click state 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"
}
我们可以看到很多字段是空,因为文字更新是比较简单的。但是它有很多属性字段,因为当你移动节点就会比仅仅更新字符串要复杂得多。我们来看这部分的源码加深理解。
//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
,因此实际上这是最后一步,我们调用步骤 (3) 的 setTextContent
方法并且更新 HTML 节点(从真实 DOM 中操作)。
非常好!内容已经被更新,界面上也做了重绘。我们还有什么遗忘的吗?让我们结束更新!这些事都做完了,我们的组件生命周期钩子函数 componentDidUpdate
会被调用。这样的延迟回调是怎么调用的呢?实际上就是通过事务的封装器。如果你还记得,脏组件的更新会被 ReactUpdatesFlushTransaction
封装器修饰,并且其中的一个封装器实际上包含了 this.callbackQueue.notifyAll()
逻辑,所以它回调用 componentDidUpdate
。很好,现在看上去我们已经讲完了全部内容。
我们来回顾一下我们学到的。我们再看一下这种模式,然后去掉冗余的部分:
14.2 第 14 部分简化板 (点击查看大图)
然后我们适当再调整一下:
14.3 第 14 简化和重构 (点击查看大图)
很好,实际上,下面的示意图就是我们所讲的。因此,我们可以理解第 14 部分的本质,并将其用于最终的 updating
方案:
14.4 第 14 部分 本质 (点击查看大图)
我们已经完成了更新操作的学习,让我们重头整理一下。
14.5 更新 (点击查看大图)