Axiu Blog
过程,但是没展开。本周趁着还熟,这篇填坑。 首先,简单画一下本篇的数据结构 ![dom-render](https://imgs.axiu.me/2018s4/dom-render.svg) 前面挂载一篇说过,从`ReactCompositeComponent`一步一步调用`mountComponent`,最后会到最内层的`ReactDomCompo
在【[React-简单组件的挂载(mount)过程](https://axiu.me/coding/react-basic-component-mount/)】的2.6)里提到了组件最后从`ReactElement`到HTML-DOM的转换过程,但是没展开。本周趁着还熟,这篇填坑。 首先,简单画一下本篇的数据结构 ![dom-render](https://imgs.axiu.me/2018
在【[React-简单组件的挂载(mount)过程](https://axiu.me/coding/react-basic-component-mount/)】的2.6)里提到了组件最后从`ReactElement`到HTML-DOM的转换过程,但是没展开。本周趁着还熟,这篇填坑。 首先,简单画一下本篇的数据结构 ![dom-render](https://imgs.axiu.me/2018
React-简单组件到浏览器DOM的渲染
Max

在【React-简单组件的挂载(mount)过程】的2.6)里提到了组件最后从ReactElement到HTML-DOM的转换过程,但是没展开。本周趁着还熟,这篇填坑。

首先,简单画一下本篇的数据结构
dom-render

前面挂载一篇说过,从ReactCompositeComponent一步一步调用mountComponent,最后会到最内层的ReactDomComponent.mountComponent,本篇直接从这里往下面走。

-ReactDomComponent.mountComponent -ReactDOMComponentTree.precacheNode(this, el) // 创建双向链接,el = ownerDocument.createElement('div');

接着根据el信息创建用于渲染的主树(lazyTree)。

-var lazyTree = DOMLazyTree(el);

至于这里为什么叫lazyTree?这部分的说明放到最后。

实例化子节点

接着开始依次实例化子节点。并最后填充到主树上。

-this._createInitialChildren(transaction, props, context, lazyTree); // 填充主lazyTree -this.mountChildren(childrenToUse, transaction, context); //childrenToUse == ['Hello', 'world', '!'] -ReactMultiChild._reconcilerInstantiateChildren // 实例化children

-ReactChildReconciler.instantiateChildren(nestedChildren, transaction, context, selfDebugID);

traverseAllChildren // 前面一篇提到的递归,在这里 -instantiateChild -instantiateReactComponent

-ReactHostComponent.createInstanceForText(node); // 依次实例化children节点

这一通操作之后,会取到最终实例化的子节点树,由一个个的ReactDOMTextElement组成。

children = { .0: ReactDOMTextElement{ "_currentElement": "Hello ", "_stringText": "Hello ", ... "_debugID": 3 } .1: {"_currentElement": "World",...} .2: {"_currentElement": "!",...} }

-ReactReconciler.mountComponent // 对children循环调用 -ReactDOMTextComponent.mountComponent // 返回3个节点各自独立的子树

DOMLazyTree.queueChild(lazyTree, mountImages[0~2]); // 子树插入父级lazyTree

最后父级的lazyTree长这样

{ ... node: #document-fragment { childNodes: { // NoeList #comment-open:'react-text: 2' #'Hello ' #comment-close: '/react-text' ...'world'... ...'!'... } } ... }

如果用f12查看元素,会看到,最终的节点是由两段react-text注释包起来的

Hello

为什么要用Comment包起来?这部分也放在最后说。

渲染浏览器DOM

在这一步操作之后,会逐层返回,最后调用最外层的

ReactMount._mountImageIntoNode( markup, // 上一步的父级lazyTree container, // #container DOM节点 wrapperInstance, shouldReuseMarkup, transaction );

这里分为2个小步骤:

  1. removeChild递归清空container容器内容;
  2. #container节点上调用insertBefore插入lazyTree.node

这样,到浏览器的DOM就渲染完成了。

小记

1、为什么需要lazyTree?

lazyTree主要解决的是在IE(8-11)和Edge浏览器中,插入节点的效率问题。总的来说,在上述IE系列浏览器中,一个一个插入无子孙的节点,效率要远高于插入一整个序列化完整的节点树。

关于二者的效率差异和说明,可以看这里:innerHTML vs. createElement vs. cloneNode

具体来说,在上述IE系列里,从最“孙子”的节点开始往上,挨个parentNode.insertBefore,会比较快。在非浏览器里,可以从下网上,parentNode.appendChild,效率也很高。差别很明显,一个自顶向下,一个自下向上。所以可以看到lazyTree生成的结构是这样

lazyTree = { node: el, children: [], html: null, text: null, toString: toString }

这个结构里,node用于非IE浏览器的DOM操作,里面直接就是搞好的HTML节点。而children用于IE系列,因为需要记录下来回去的路径,才能遍历。

说明二者操作差异的函数,在DOMLazyTree.queueChild

function queueChild(parentTree, childTree) { if (enableLazy) { parentTree.children.push(childTree); } else { parentTree.node.appendChild(childTree.node); } }

最后insertTreeBefore,会判断是操作children插入节点,还是直接插入node

2、为什么要用Commenttext包起来?

答案是避免不必要的遍历。

前面可以看到,每一个ReactDOMTextComponent都包含一个指向Comment的链接,反向链接也由。这就相当于从虚拟节点到实际节点的双向链接,以保证每个节点都是立即可达的。

但是,单纯的一段text,没有办法添加附加属性来创建反向链接,所以这里使用了Comment来承载节点信息。

关于Comment的创建,使用的是Document.createComment(),可能这个方法不常用,具体说明可看MDN: createComment

Comments