Que's Blog

WebFrontEnd Development

0%

react ReactDOMComponent分析

ReactDOMComponent

虽然整体的逻辑看起来异常复杂,但是仔细分析,实际上它处理的事情也仍然很清晰。

这件简要分析

line461-line482简单的定义了ReactDOMComponent内部的私有变量。

line1203-line1207的Object.assign则很简单将ReactDOMComponent.Mixin&ReactMultiChild.Mixin复制到ReactDOMComponent的原型上。所以这里需要对ReactDOMComponent.Mixin&ReactMultiChild.Mixin做一些分析

ReactDOMComponent.Mixin

API 说明
mountComponent 生成根标记然后递归生成内部标记。这个生成是在事务支持下的生成(更新前卸载事件更新后重新绑定诸如此类)。这个操作不是冥等的,多次执行结果并不完全一致。
_createOpenTagMarkupAndPutListeners 生成html标记的openTag markup并处理事件绑定
_createContentMarkup 为html标签内的内容创建对应的markup
_createInitialChildren 遍历下级children标记放到目标元素中
receiveComponent 更新_currentElement并调用this.updateComponent更新组件
updateComponent 使用_updateDOMProperties & _updateDOMChildren更新props、styles和children
_updateDOMProperties 更新styles和props
_updateDOMChildren 更新children
getHostNode 获取组件对应的HTMLElement节点
unmountComponent 卸载事件注册 但不直接影响DOM
getPublicInstance 获取组件对应的HTMLElement节点 同getHostNode

mountComponent

生成根标记然后递归生成内部标记。这个生成是在事务支持下的生成(更新前卸载事件更新后重新绑定诸如此类)。这个操作不是冥等的,多次执行结果并不完全一致。

关于事务的分析,我们后面再总体分析一下看看,这里简单看看这里的遍历生成标记是如何做到的。

首先看看mountComponent整体执行完毕后的返回数据结构

其次,这个函数里面有两行非常关键的代码,这是遍历生成标记的核心调用

1
2
var lazyTree = DOMLazyTree(el);
this._createInitialChildren(transaction, props, context, lazyTree);

这里看看lazyTree返回值:

1
2
3
4
5
6
7
8
9
function DOMLazyTree(node) {
return {
node: node,
children: [],
html: null,
text: null,
toString,
};
}

然后这里接下来先看看_createInitialChildren

_createInitialChildren

它的作用是遍历下级children标记放到目标元素中

先看看大致逻辑和参数问题,这里有transaction, props, context, lazyTree

这里props是实例化ReactDOMComponent时候传入的element,transaction、context这里不再讲,lazyTree是上一步传入。此时props上显然会有children保存其子元素的数组。

这里有两个逻辑分支

  • props.children 是string|number -> contentToUse 此时DOMLazyTree.queueText(lazyTree, contentToUse)

  • 否则childrenToUse执行

  • 下一步:

    1
    2
    3
    4
    5
    6
    7
    8
    var mountImages = this.mountChildren(
    childrenToUse,
    transaction,
    context,
    );
    for (var i = 0; i < mountImages.length; i++) {
    DOMLazyTree.queueChild(lazyTree, mountImages[i]);
    }

这个this.mountChildren在ReactMultiChild.Mixin被集成到原型上来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
mountChildren: function(nestedChildren, transaction, context) {
var children = this._reconcilerInstantiateChildren(
nestedChildren,
transaction,
context,
);
this._renderedChildren = children;
var mountImages = [];
var index = 0;
for (var name in children) {
if (children.hasOwnProperty(name)) {
var child = children[name];
var selfDebugID = 0;
var mountImage = ReactReconciler.mountComponent(
child,
transaction,
this,
this._hostContainerInfo,
context,
selfDebugID,
);
child._mountIndex = index++;
mountImages.push(mountImage);
}
}
return mountImages;
},

这里暂不用去理会this._reconcilerInstantiateChildren只是简单提一下,它是一个数组,里面的元素的结构和lazyTree返回值是一致的。

仅仅看ReactReconciler.mountComponent就可以知道,mountComponent里面说到的这个遍历,便是从这里而来。他的流程是:

1
2
3
4
5
6
ReactDOMComponent.mountComponent 
-> ReactDOMComponent._createInitialChildren
-> ReactMultiChild.Mixin.mountChildren
-> ReactReconciler.mountComponent
-> instantiateReactComponent
-> ReactDOMComponent.mountComponent

至于ReactReconciler.mountComponent 到 ReactDOMComponent.mountComponent这个环节,请参考上文ReactReconciler.mountComponent。

这里的遍历是起到这样的作用。我们知道jsx转换到js之后是近似

的嵌套结构,如果有子组件那么也是这样的结构放在props.children上,以此类推。

而这里的遍历则是将这个结构转换成

1
2
3
4
5
6
7
{
node: node,
children: [],
html: null,
text: null,
toString,
}

这样的结构,子元素放到children数组中。只不过此时这里的node已经是HTML元素了可以直接插入到DOM中。

这时候使用ReactMount._mountImageIntoNode就可以将其渲染到DOM了。

PS: 需要注意的,这个转换仅仅是在transaction.useCreateElement成立(在这里,服务端渲染才会为fasle,这里暂不考虑这块),否则回直接返回html标记字符串。

_updateDOMChildren

_updateDOMChildren这个方法是当children更新后的调用。它在updateComponent环节被调用过。

在这个函数中有个调用非常关键 this.updateChildren,它从ReactMultiChild.Mixin继承而来。这里设计到了diff算法处理,暂且放下。

_createOpenTagMarkupAndPutListeners

这个函数可以返回一个<div class="name" />代码中的<div class="name"这个部分。并对事件进行绑定。服务端渲染会用到。

_createContentMarkup

为html标签内的内容创建对应的markup。服务端渲染会用到。

和_createOpenTagMarkupAndPutListeners有一些关联,前者是获得openTagMarkup,这个获取标记中间的元素内容Markup。

updateComponent

1
2
this._updateDOMProperties(lastProps, nextProps, transaction);
this._updateDOMChildren(lastProps, nextProps, transaction, context);

unmountComponent

关键代码是:

1
2
3
this.unmountChildren(safely);
ReactDOMComponentTree.uncacheNode(this);
EventPluginHub.deleteAllListeners(this);

主要涉及了ReactDOMComponentTree、EventPluginHub、ReactMultiChild三个模块

getHostNode

调用了ReactDOMComponentTree.getNodeFromInstance获取组件对应的HTMLElement节点。这个模块后面分析。

_updateDOMProperties

这个函数其实还挺重要的。在我们的使用过程中,我们在props上放style放className 放refs放onClick。这些都会得到体现。

这里有三个逻辑分支

  • style的处理
  • 事件注册的处理[registrationNameModules.hasOwnProperty(propKey)]。先解绑后重新绑定。关键函数enqueuePutListener
  • 自定义标签的处理