本文较为基础,适合react新手阅读。阅读过程中如果发现任何错误,麻烦评论区指正。
React版本:React17.0.0。
引言
由于项目渲染多个根组件或者通过多次render的方式进行更新等情况不常见,所以本文跳过update阶段相关的逻辑,只探讨React初次渲染阶段且只渲染当个根组件的情况。
ReactDOM.render
/**
* @param element - 要渲染的 React 元素
* @param container - 要进行渲染的 DOM 元素
* @param callback - 在渲染完成后要执行的函数
* @return container - 包含已渲染组件的容器
*/
function render(element, container, callback) {
// ...
return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
}
render函数中主要关注legacyRenderSubtreeIntoContainer函数。
legacyRenderSubtreeIntoContainer
function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
var root = container._reactRootContainer;
var fiberRoot;
if (typeof callback === 'function') {
var originalCallback = callback;
callback = function () {
var instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
} //处理回调
if (!root) {
// 初次渲染,如果没有root,则去创建root
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
fiberRoot = root._internalRoot;
unbatchedUpdates(function () {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
//...
}
return getPublicRootInstance(fiberRoot);
}
首先会获取root,根据root的值来判断是否是mount阶段。
下边是container的结构:
{
_reactRootContainer:{
_internalRoot:FiberRoot
}
}
结构中_internalRoot对应的值就是项目的FiberRoot,是项目的Fiber根节点,一个项目只会有一个,是唯一的。 如果项目是初次渲染的话,root不存在,则会进入到创建root、FiberRoot和rootFiber的逻辑中,也就是legacyCreateRootFromDOMContainer方法。
legacyCreateRootFromDOMContainer
在该方法中会一路调用:createLegacyRoot -> new ReactDOMBlockingRoot -> createRootImpl -> createContainer -> createFiberRoot
调用的层级可能会有点深,但是可以将legacyCreateRootFromDOMContainer主要做的事情可以总结为如下代码:
function legacyCreateRootFromDOMContainer(container, forceHydrate) {
//创建FiberRoot节点
var root = new FiberRootNode(containerInfo, tag, hydrate);
//根据项目启动的不同类型,创建不同类型的RootFiber,核心仍然是创建RootFiber(tag为3)。
var uninitializedFiber = createHostRootFiber(tag);
// 建立FiberRoot与RootFiber之间的联系
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
// 对于HostRoot或者ClassComponent会使用initializeUpdateQueue创建updateQueue,
// 然后将updateQueue挂载到fiber节点上
initializeUpdateQueue(uninitializedFiber);
return root;
}
//updateQueue是React状态更新相关的数据结构
function initializeUpdateQueue(fiber) {
var queue = {
baseState: fiber.memoizedState,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null
},
effects: null
};
fiber.updateQueue = queue;
}
上述代码作用的是current Fiber树,current Fiber经过创建后,是存在fiberRoot节点和rootFiber节点的。这也就是说后续render阶段,处理workInProgress Fiber树的RootFiber节点不需要创建,而是通过Fiber双缓存的alternate指针获取Current Fiber树的RootFiber节点进行复用更新处理。
updateContainer
在创建好FiberRoot和rootFiber之后,就会走到updateContainer函数,
function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
{
//...
unbatchedUpdates(function () {
updateContainer(children, fiberRoot, parentComponent, callback);
});
//...
}
在创建完FiberRoot和RootFiber之后,会以非批量更新的形式执行updateContainer方法,无需等待 React 的批量更新机制,同步执行。
function updateContainer(element, container, parentComponent, callback) {
// current$1为Current Fiber树的RootFiber。
// 参数container为FiberRoot,FiberRoot.current则指向的就是之前创建的RootFiber
var current$1 = container.current;
var eventTime = requestEventTime();
var lane = requestUpdateLane(current$1);
// 创建一个update对象,payload为根组件的jsx对象。
var update = createUpdate(eventTime, lane);
update.payload = {
element: element
};
//处理render传入的回调,当该update更新后进行的回调
callback = callback === undefined ? null : callback;
if (callback !== null) {
{
if (typeof callback !== 'function') {
error('render(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback);
}
}
update.callback = callback;
}
//将update插入到fiber节点的updateQueue中
enqueueUpdate(current$1, update);
//调度Current RootFiber节点的更新
scheduleUpdateOnFiber(current$1, lane, eventTime);
return lane;
}
enqueueUpdate的作用就是将刚刚创建好的update加入到RootFiber的updateQueue.pending中,等到进入render阶段处理updateQueue创建根组件的Fiber节点。
function enqueueUpdate(fiber, update) {
var updateQueue = fiber.updateQueue;
if (updateQueue === null) {
// Only occurs if the fiber has been unmounted.
return;
}
var sharedQueue = updateQueue.shared;
var pending = sharedQueue.pending;
// 这是一个环形链表的处理,将新创建的update插入到环形链表的尾部,
// 并将尾指针(sharedQueue.pending)进行更新。
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
}
scheduleUpdateOnFiber
function scheduleUpdateOnFiber(fiber, lane, eventTime) {
var root = markUpdateLaneFromFiberToRoot(fiber, lane);
if (root === workInProgressRoot) {
var priorityLevel = getCurrentPriorityLevel();
if (lane === SyncLane) {//同步 legacy模式
performSyncWorkOnRoot(root);
} else {// concurrent模式
ensureRootIsScheduled(root, eventTime);//将performConcurrentWorkOnRoot函数调度执行
}
mostRecentlyUpdatedRoot = root;
}
最终都会执行performSyncWorkOnRoot或者performConcurrentWorkOnRoot进入到render模式。
总结
ReactDOM.render到render阶段之间的流程是:
- 创建FiberRoot和RootFiber节点,并将二者建立起联系(FiberRoot.current=RootFiber)
- 初始化RootFiber的updaateQueue,并创建payload为根组件的update对象加入到updateQueue中
- 开始调度root的更新,根据mode的不同,执行不同mode的启动render阶段的方法