react-dom后续updateContainer部分。阅读React包的源码版本为16.8.6。
在上一章节中我们看到了react-dom
中render
函数的逻辑是给传入的React组件创建了一个fiberRoot
对象,用于标识它是整个应用的起点,上面拥有很多应用更新相关的表示符。然后创建对应的fiber
给fiberRoot
节点,fiber
对象是每一个ReactElement
都拥有的节点,它标识了更新时间的一些信息,props和state的一些信息,以及相关联的节点信息。ReactElement
彼此是通过一个单向链表的数据结构联系在一起的。
这一章我们接着legacyRenderSubtreeIntoContainer
函数创建完fiber
相关对象的部分,查看接下来updateContainer
相关的逻辑。我们先回顾一下这部分的代码。
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
// 是否复用dom节点,服务端渲染调用
forceHydrate: boolean,
callback: ?Function,
) {
// ...省略创建fiber节点相关部分逻辑
// 初次使用render不存在root节点
if (!root) {
// ...省略创建fiber节点相关部分逻辑
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
fiberRoot = root._internalRoot;
// 有无callback 逻辑同上
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Update
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(fiberRoot);
}
我们先看callback
部分的处理,在render
函数中callback
是传入的第三个参数,根据react文档
,该回调将在组件被渲染或更新之后被执行,并且在非箭头函数的情况下,该回调的this指向render
渲染的那个组件。我们先来回顾下这个入参的使用方式。
const instance = render(
<Hello text="123" />,
document.getElementById("page"),
function () {
console.log(this) }
);
console.log(instance);
/*
this === instance === Hello
Hello {
isMounted: (...)
replaceState: (...)
props: {text: "123"}
context: {}
refs: {}
updater: {isMounted: ƒ, enqueueSetState: ƒ, enqueueReplaceState: ƒ, enqueueForceUpdate: ƒ}
_reactInternalFiber: FiberNode {tag: 1, key: null, stateNode: Hello, elementType: ƒ, type: ƒ, …}
_reactInternalInstance: {_processChildContext: ƒ}
state: null
__proto__: Component
}
*/
在上述代码中使用render
函数时,传入了一个匿名函数作为render
的第三个入参,并打印了this
,然后将render
函数的返回值赋予了instance
变量并打印出来。我们可以看到,输出的是一个对象信息,其实使用过react
测试相关诸如react-test-renderer
等框架的,应该对这个instance
比较熟悉。它标志了一个由fiberRoot
开始的完整的组件信息。
现在回到源码,我们可以看到我们在调用render
时候,callback
的this和render
返回的组件instance
信息都是由getPublicRootInstance
创建的。react
将我们传入的callback
赋值给了变量originalCallback
,然后声明一个新的callback
,新的callback
创建了一个instance
,然后用call
让originalCallback
的this指向它,把它传入到了updateContainer
的callback
参数中。
至于getPublicRootInstance
如何创建一个Instance
的细节代码与主流程牵扯不大,这边就跳过。只要知道该函数根据fiberRoot
提供了一个Instance
信息对象即可。接着我们可以看到,无论是否是初次使用render
函数(初次调用render函数不存在root节点),legacyRenderSubtreeIntoContainer
都调用了updateContainer
方法,区别就是初次使用render
的时候,updateContainer
是在unbatchedUpdates
方法回调中使用的。unbatchedUpdates
做的事情实际就是在render
初次调用的时候,不用去批量更新updateContainer
,这个函数做的事情仅仅是改变了几个标志符,然后立即更新了CallbackQueue
,我们也略过这部分逻辑,重点来看一下updateContainer
相关的部分。
function updateContainer(
element: ReactNodeList,
container: OpaqueRoot, // root
parentComponent: ?React$Component<any, any>, // 根节点是null
callback: ?Function,
): ExpirationTime {
// fiberRoot的current为fiberRoot的fiber对象
const current = container.current;
// 获得当前时间到js加载完时间的时间差值