Que's Blog

WebFrontEnd Development

0%

React ReactDOMComponentTree

简述

ReactDOMComponentTree主要是对DOM Node 和 ConponentInst做一些映射,不过这个映射不通过Map结构来处理,而是通过在它们自身上面挂载属性做缓存的方式来实现。

这样当给出一个DOM Node 或者 ConponentInst时候 就可以对应给出它们对应的DOM Node 或者 ConponentInst了。

API

方法名 作用
getClosestInstanceFromNode 根据传入的DOM Node返回临近的(实际上是往上找)ReactDOMComponent or ReactDOMTextComponent
getInstanceFromNode 根据传入的DOM Node返回对应的ReactDOMComponent or ReactDOMTextComponent实例 可能返回null
getNodeFromInstance 根据传入的组件实例获得对应的DOM Node
precacheChildNodes 为所有的子节点实例添加_hostNode属性进行缓存, 这个缓存操作是双向的
precacheNode 使用给定的DOM node填充在给定的实例的_hostNode属性上
uncacheNode 将给定实例的_hostNode设为null

这之前,需要先弄清楚,DOM Node和Instance是什么。DOM Node这个不提默认大家知道,Instance这个,可以参见instantiateReactComponent返回的几种实例。

细节

getRenderedHostOrTextFromComponent

1
2
3
4
5
6
7
function getRenderedHostOrTextFromComponent(component) {
var rendered;
while ((rendered = component._renderedComponent)) {
component = rendered;
}
return component;
}

这个函数被导出多个方法调用到。它的作用是从给定的component(自定义组件或者空组件)一直向下钻,直到获取到一个component._renderedComponent === void 0的组件并返回(如果一开始就满足这个条件就直接返回了传入的componet)。这个过程中如果component上已经有_renderedComponent那么它会被赋值给component并再次重复这个步骤。

precacheNode

1
2
3
4
5
function precacheNode(inst, node) {
var hostInst = getRenderedHostOrTextFromComponent(inst);
hostInst._hostNode = node;
node[internalInstanceKey] = hostInst;
}

代码就三行,但是这里做了三件事:

  • 获取hostInst 从inst上一直往下钻获取_renderedComponent 参见上面的分析
  • 在实例上绑定_hostNode将其设为DOM Node
  • 在DOM Node上将inst使用internalInstanceKey做key挂载好

所以这里的缓存是两步: 在DOM Node上缓存inst的同时也在实例上缓存了DOM Node

uncacheNode

1
2
3
4
5
6
7
function uncacheNode(inst) {
var node = inst._hostNode;
if (node) {
delete node[internalInstanceKey];
inst._hostNode = null;
}
}

precacheNode的反向操作。双向解绑。

precacheChildNodes

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
28
function precacheChildNodes(inst, node) {
if (inst._flags & Flags.hasCachedChildNodes) {
return;
}
var children = inst._renderedChildren;
var childNode = node.firstChild;
outer: for (var name in children) {
if (!children.hasOwnProperty(name)) {
continue;
}
var childInst = children[name];
var childID = getRenderedHostOrTextFromComponent(childInst)._domID;
if (childID === 0) {
// We're currently unmounting this child in ReactMultiChild; skip it.
continue;
}
// We assume the child nodes are in the same order as the child instances.
for (; childNode !== null; childNode = childNode.nextSibling) {
if (shouldPrecacheNode(childNode, childID)) {
precacheNode(childInst, childNode);
continue outer;
}
}
// We reached the end of the DOM children without finding an ID match.
invariant(false, 'Unable to find element with ID %s.', childID);
}
inst._flags |= Flags.hasCachedChildNodes;
}

这个函数用处是遍历所有下级children并为其调用precacheNode进行缓存。

这个遍历是遍历当前实例下一级且只有这一级的children。缓存完毕之后更新_flags,这里的运算是位或运算,只要inst._flags、Flags.hasCachedChildNodes之一为1那么为1,否则同时为0就为0.

这个缓存过程是为了避免算法纬度达到n^2

这里注释里面提到这里代码存在的BUG: inst._renderedChildren更新和这里的缓存会存在赛跑问题,所以更改它之前会调用prepareToManageChildren来避免这种情况。不过就当前观察的版本15.6.2来说这个函数已经被移除了。后面会分析一下当前版本是怎样做的。

getClosestInstanceFromNode

这个函数逻辑有些小绕。它的逻辑是:

  • 判断传入的DOM Node是否已经挂载internalInstanceKey属性,有则返回 否则下一步
  • 一直往上找自己的父元素,直到找到一个已经被缓存internalInstanceKey属性的父元素
  • 返回这个找到的元素,如果之前向上溯源超过两级就使用precacheChildNodes(inst, node)对这个元素的child进行缓存操作