1

接上文,

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会被调用。完美!

现在,我们真的完成了整个过程。


座大山
83 声望15 粉丝