Que's Blog

WebFrontEnd Development

0%

React核心:Reconciler调度模块

Reconciler范畴

关注点放到src/renders/shared/stack/reconciler上来。这里Reconciler模块的实现代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
renderers/shared/stack/reconciler
├── ReactChildReconciler.js
├── ReactComponentEnvironment.js
├── ReactCompositeComponent.js
├── ReactDefaultBatchingStrategy.js
├── ReactEmptyComponent.js
├── ReactEventEmitterMixin.js
├── ReactHostComponent.js
├── ReactInstanceType.js
├── ReactMultiChild.js
├── ReactMultiChildUpdateTypes.js
├── ReactNodeTypes.js
├── ReactOwner.js
├── ReactReconciler.js
├── ReactRef.js
├── ReactSimpleEmptyComponent.js
├── ReactUpdateQueue.js
├── ReactUpdates.js
├── getHostComponentFromComposite.js
└── instantiateReactComponent.js

getHostComponentFromComposite

用于从自定义组件上获取实例上的_renderedComponent,如果自定义组件不断相互嵌套使用,他还会不断往下深挖,直到返回的是ReactDomComponent节点为止,如果为空返回null。

这个函数可以反馈组件架构上的涉及:

  • _renderedComponent保存children节点

  • _renderedNodeType保存节点类型

    1
    2
    3
    4
    5
    Enum ReactNodeTypes {
    HOST: 0,
    COMPOSITE: 1,
    EMPTY: 2,
    }

instantiateReactComponent

根据给出的ReactNode返回一个组件实例。

小小研究一下这个函数,其实可以窥见ReactNode和React组件实例之间的关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type ReactDOMElement = { 
type : string,
props : {
children : ReactNodeList,
className : string,
etc.
},
key : string | boolean | number | null,
ref : string | null
};

type ReactComponentElement<TProps> = {
type : ReactClass<TProps>,
props : TProps,
key : string | boolean | number | null,
ref : string | null
};

对于ReactDOMElement来说,type是字符串
对于ReactComponentElement来说, type是一个函数

这里这个函数根据type属性做了4个分支

  • 如果是string,使用ReactHostComponent.createInternalComponent(element)返回ReactDOMElement
  • 如果是function且已知(定义好了mountComponent|receiveComponent这些),那么调用type(自定义组件的render函数)
  • 如果是function且未知,使用new ReactCompositeComponentWrapper(element)返回一个ReactCompositeComponent包装结构。
  • 如果这个节点没有type属性直接是一个string|number,那么返回一个文本节点

ReactChildReconciler

这个函数是Diff算法的一部分。主要是Element Diff部分。

1
2
3
4
5
type ReactChildReconciler {
instantiateChildren: () => {} // 返回新的Children实例对象
updateChildren: () => {} // 进行内部Diff更新 三种算法都会综合使用
unmountChildren: () => {} // 调用internalInstance.unmountComponent,移除ref引用
}

这里updateChildren是diff算法的核心实现。

ReactComponentEnvironment

代码相关引用路径如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var ReactComponentEnvironment = {
replaceNodeWithMarkup: (null: ?ReplaceNodeWithMarkup),
processChildrenUpdates: (null: ?ReplaceNodeWithMarkup),
}
// ReactDefaultInjection.js
ReactInjection.Component.injectEnvironment(ReactComponentBrowserEnvironment);
// ReactComponentBrowserEnvironment.js
var DOMChildrenOperations = require('DOMChildrenOperations');
var ReactDOMIDOperations = require('ReactDOMIDOperations');

var ReactComponentBrowserEnvironment = {
processChildrenUpdates: ReactDOMIDOperations.dangerouslyProcessChildrenUpdates,
replaceNodeWithMarkup: DOMChildrenOperations.dangerouslyReplaceNodeWithMarkup,
};
module.exports = ReactComponentBrowserEnvironment;

这里为Reconciler模块抽象出了需要了解浏览器上下文的功能。这些都是一些实质性对浏览器DOM进行操作的函数。

这里先说processChildrenUpdates,它主要引用了DOMChildrenOperations.processUpdates,关于这个函数,参考Diff算法-更新到DOM。因为这里是Diff操作后,将虚拟dom变化反馈到实质Dom的流程,所以必须按照浏览器的DOM API来进行dom更新,这里必须知道浏览器上下文。

其次是replaceNodeWithMarkup,它指向了DOMChildrenOperations.DOMChildrenOperations。这个函数用途也是在Diff环节中的,我们知道,如果一个组件没有变化只是props更新,那么它会走更新逻辑,但是变化了呢?它会直接删除旧的+替换为成新生成的,这就是这个API的功能。这里替换都是用的replaceChild,暂时也没看到和浏览器兼容相关代码,此处兼容相关的是insertTreeChildren

ReactCompositeComponent

这个是自定义组件的相关代码,这里不再说细节。

主要是提一下,自定义组件也是Reconciler模块的一部分。

ReactDefaultBatchingStrategy

这个模块是批量更新的处理。主要涉及到3个部分: 事务、更新。

两个事务在于:

1
2
3
4
5
6
7
8
9
10
11
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function() {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
},
};

var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};

前者是模块事务操作,后者是更新事务。其中模块事务完毕后要重设isBatchingUpdates=false;

这个isBatchingUpdates变量是事务过程中需要使用的变量:当没有事务执行中的时候直接执行,否则以事务方式执行这个函数(先执行initializeAll, 函数完毕再执行closeAll)。

核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
/**
* Call the provided function in a context within which calls to `setState`
* and friends are batched such that components aren't updated unnecessarily.
*/
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// The code is written this way to avoid extra allocations
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
},
};

更新事务则关注ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)。这里值得扯一扯的是ReactUpdates.batchedUpdates实质上指向了这个ReactDefaultBatchingStrategy.batchedUpdates,最后又根据事务反向指向回去了flushBatchedUpdates

具体细节在《React ReactUpdates》里面有抽丝剥茧式得剖析,这里不再赘述。

只是做宏观讲述,这个batchedUpdates最后是对所有脏组件(dirtyComponents),调用其自身receiveComponent函数走Diff,然后反馈到浏览器UI。

这里倒是可以提一提dirtyComponents的维护。直接调用是ReactUpdates.enqueueUpdate,这里有简单的说明

它这里有一个主要维护路径:

1
2
3
4
props更新(假如有)
->this.setState
-->this.updater.enqueueSetState || this.updater.enqueueCallback
--->ReactUpdates.enqueueUpdate

这里就是脏组件的维护路径了。关于this.updater的赋值,在ReactCompositeComponent.js,line 255, mountComponent函数中,而不是setState函数里面默认的那一个。

ReactEmptyComponent

这个地方无甚可说,就是返回一个ReactDOMEmptyComponent

ReactEventEmitterMixin

这个函数作用是从委托到Document上的回调集合里面找到对应当前组件的事件,然后对事件进行分发。

关于这个部分,可以参考React Event。这里直接链入的是核心调用分析环节,如果想有一个全面了解,还是必须整体通读这一篇。

ReactHostComponent

1
2
3
4
5
6
var ReactHostComponent = {
createInternalComponent: createInternalComponent,
createInstanceForText: createInstanceForText,
isTextComponent: isTextComponent,
injection: ReactHostComponentInjection,
};

主要就是两个函数:

createInternalComponent => new ReactDOMComponent(element)

createInstanceForText => new ReactDOMTextComponent(text)

这里需要明白,ReactNode和组件实例之间的区别。

ReactMultiChild

ReactMultiChild是Diff算法环节中重要一环。

这里因为Diff算法已经先总结好了,直接去React Diff算法看即可

ReactOwner

这个模块和ReactCompositeComponent是有很大关联性的。

这里具体参看之前的文章:ReactCurrentOwner:ReacCompositeComponent & ReactDomComponent之间的胶水

至于除此之外的两个调用:

1
2
owner.attachRef(ref, component);
owner.detachRef(ref);

都是对ReactInstance的属性调用。这两个函数是对ref这个props的处理。

这里涉及的是ReactRef模块

ReactRef

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function attachRef(ref, component, owner) {
if (typeof ref === 'function') {
ref(component.getPublicInstance());
} else {
// Legacy ref
ReactOwner.addComponentAsRefTo(component, ref, owner);
}
}

function detachRef(ref, component, owner) {
if (typeof ref === 'function') {
ref(null);
} else {
// Legacy ref
ReactOwner.removeComponentAsRefFrom(component, ref, owner);
}
}

这个ref经常用到,函数也简单到可以靠猜,所以不再赘述了。

主要提一下其调用。它们在ReactReconciler.receiveComponentReactReconciler.unmountComponent分别有相关调用。

ReactSimpleEmptyComponent

这个函数是给ReactNative用的,这里我们就不管了。

ReactUpdates

关于这个ReactUpdates,可以直接看之前的一篇, React ReactUpdates, 这里有详细的说明。

ReactUpdateQueue

ReactUpdates是一个队列的执行,但是既然是队列,那么他还是需要一个角色来维护这个队列,以便后面任务有序加入更新队列里。这里ReactUpdateQueue起到的作用就是维护这个队列。

这里还是有些细节可以看,不过这里暂时略过,后面加以补充。

体系&作用

上面主要提到了构成Reconciler体系的各个小模块。

但是这些在整个React体系里面是怎么发挥其核心作用的呢?这里需要一张图,来简要诠释,在整个React体系中,它是怎么居中调配React各个功能模块,使它井然有序工作的。

React-Reconciler