Que's Blog

WebFrontEnd Development

0%

React ReactCurrentOwner

最前面

这里先说明白,这个ReactCurrentOwner实际上是在虚拟DOM中识别自定义组件的关键。

当我们执行了自定义组件的render,返回的虚拟DOM树里面的节点,不算文本节点、注释节点这类,它就是完全有ReactDomComponent节点组成的,这个ReactCurrentOwner.owner变量,保存了一个瞬时变量,用于存到自定义组件生成的ReactDomComponent节点的_owner变量上。

作用

ReactCurrentOwner这个主要还是和自定义组件有关系。

使用grep -rn 'ReactCurrentOwner.current =' ./src全局查了以下。赋值操作主要是在

ReactMultiChild.jsReactCompositeComponent.js文件中。

它有三种赋值情景:

  • ReactCurrentOwner.current = this._currentElement._owner
  • ReactCurrentOwner.current = this
  • ReactCurrentOwner.current = null

情景1

其中,情景一this._currentElement._owner仅在开发环节会执行

情景2

大部分情况下,一课虚拟DOM树大致由一个TopLevelWrapper组件作为根元素,然后子节点里面ReactCompositeComponent和ReactDomComponent互相交织嵌套,最后以ReactDomComponent为最末节点的结构组成的(这里假装不知道文本在React里面也算节点。。。)。

我们写的业务代码、包含各种生命周期的组件,基本都是ReactCompositeComponent,

ReactCurrentOwner.current也都是指向了这个类型的组件。

在ReactCompositeComponent组件里面存在生命周期,state,props,context这些东西,也存在更新时候的Diff算法优化,如果一个ReactDomComponent组件确定要更新,这里是替换式无脑更新的,不存在Diff算法什么的。

这种情况下,ReactCurrentOwner.current 是指当前正处于构建过程中的组件。

这个变量实际上相当于是一个存在于React作用域全局的一个缓存变量。

ReactCompositeComponent构建时候,ReactCompositeComponent.js里面有一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
_renderValidatedComponent: function() {
var renderedElement;
if (__DEV__ || this._compositeType !== CompositeTypes.StatelessFunctional) {
ReactCurrentOwner.current = this;
try {
renderedElement = this._renderValidatedComponentWithoutOwnerOrContext();
} finally {
ReactCurrentOwner.current = null;
}
} else {
renderedElement = this._renderValidatedComponentWithoutOwnerOrContext();
}
return renderedElement;
},

他会在render&&update时候执行。

然后就是如果从ReactCompositeComponent到ReactDomComponent这个过程中(实质上这两个类型的组件在虚拟DOM上也就是一个变量的区别)的时候,

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
var ReactElement = function(type, key, ref, self, source, owner, props) {
var element = {
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner,
};

if (__DEV__) {
// 略
} else {
element._store.validated = false;
element._self = self;
element._source = source;
}
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}

return element;
};

这个current会被缓存到Element对象中。其他时候就是对这个current做null赋值处理了。

分析

接下来就可以思考一下它在React体系中的作用了。`

1
2
3
4
5
6
7
8
9
10
11
12
13
14
_renderValidatedComponent: function() {
var renderedElement;
if (__DEV__ || this._compositeType !== CompositeTypes.StatelessFunctional) {
ReactCurrentOwner.current = this;
try {
renderedElement = this._renderValidatedComponentWithoutOwnerOrContext();
} finally {
ReactCurrentOwner.current = null;
}
} else {
renderedElement = this._renderValidatedComponentWithoutOwnerOrContext();
}
return renderedElement;
},

_renderValidatedComponent是一个很重要的函数。但是他实质上却超级简单。。。。

他就做了两件事:

  • _renderValidatedComponentWithoutOwnerOrContext——说白了就是组件的render函数调用
  • 设置ReactCurrentOwner.current为当前这个组件实例

另外,从ReactCompositeComponent到ReactDomComponent这个过程中的时候,参考一下ReactElement函数的定义:

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
var ReactElement = function(type, key, ref, self, source, owner, props) {
var element = {
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner,
};

if (__DEV__) {
// 略
} else {
element._store.validated = false;
element._self = self;
element._source = source;
}
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}

return element;
};

这个current会被缓存到Element对象中。其他时候就是对这个current做null赋值处理了。

这里归总一下:

初始render

初始化render时候会执行batchedUpdates,里面下级调用包括ReatReconciler.mountComponent,这里看看ReactCompositeComponent.mountComponent的调用。

1
2
3
ReactCompositeComponent.mountComponent
->ReactCompositeComponent.performInitialMount
-->ReactCompositeComponent._renderValidatedComponent

这里自定义组件render执行时候会不断触发其下级自定义组件的render,他们在结构上是嵌套的。

1
Component.render(SubComponent1.render(SubComponent2.render()))

关于这个逻辑,可以参考react生命周期-jsx到js的转换

他们的render是这样一个逻辑,我们想要执行Component.render实际上会先执行SubComponent1.render,进而要先执行SubComponent2.render。

通过这样的调用,我们最先执行的SubComponent2.render会先将SubComponent2赋值到ReactCurrentOwner.current,然后挂载到其下一级(孙节点和以下都不加)ReactDomComponent实例上。

以上逻辑到了SubComponent1和Component都同样进行类推。这样,所有自定义组件render完毕之后,就是一个干净的(就是一个owner变量的区别)由ReactDomComponent组成的虚拟DOM树(文本节点、注释节点等先忽视)。

而由自定义节点直接生成的ReactDomComponent节点,会有一个非null的_owner属性,指向自定义组件实例。

更新

看更新还是很有必要看看React Diff算法

看到Component Diff这一节,就基本上可以大致有数了。这里_updateRenderedComponent函数会组件更新时候必然会调用到ReactCompositeComponent._renderValidatedComponent

其他逻辑可以完全参考初始render。

总结

ReactCurrentOwner.current为什么重要?

以为它是自定义节点的指针。所有的ReactCompositeComponent最终render之后都变成了干干净净的ReactDomComponent节点组成的DOM树,但是如何分辨哪些是ReactCompositeComponent生成的呢?

这就依赖这些ReactDomComponent节点上的owner变量。

ReactCurrentOwner.current正是维护这个在构建虚拟DOM过程中,随时会变动的变量的临时保存位置所在。