本章将讲解 react 的核心阶段之一 —— render阶段,我们将探究以下部分内容的源码:
- 更新任务的触发
- 更新任务的创建
- reconciler 过程同步和异步遍历及执行任务
- scheduler 是如何实现帧空闲时间调度任务以及中断任务的
触发更新的方式主要有以下几种:ReactDOM.render
、setState
、forUpdate
以及 hooks 中的 useState
等,关于 hooks 的我们后面再详细讲解,这里先关注前三种情况。
ReactDOM.render
作为 react 应用程序的入口函数,在页面首次渲染时便会触发,页面 dom 的首次创建,也属于触发 react 更新的一种情况。
首先调用 legacyRenderSubtreeIntoContainer
函数,校验根节点 root 是否存在,若不存在,调用 legacyCreateRootFromDOMContainer
创建根节点 root、rootFiber 和 fiberRoot 并绑定它们之间的引用关系,然后调用 updateContainer
去非批量执行后面的更新流程;若存在,直接调用 updateContainer
去批量执行后面的更新流程:
// packages/react-dom/src/client/ReactDOMLegacy.js
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>, children: ReactNodeList, container: Container, forceHydrate: boolean, callback: ?Function,
) {
// ...
let root: RootType = (container._reactRootContainer: any);
let fiberRoot;
if (!root) {
// 首次渲染时根节点不存在
// 通过 legacyCreateRootFromDOMContainer 创建根节点、fiberRoot 和 rootFiber
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// 非批量执行更新流程
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// 批量执行更新流程
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(fiberRoot);
}
updateContainer
函数中,主要做了以下几件事情:
// packages/react-dom/src/client/ReactDOMLegacy.js
export function updateContainer(
element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component<any, any>, callback: ?Function,
): Lane {
// ...
const current = container.current;
const eventTime = requestEventTime(); // 获取更新触发的时间
// ...
const lane = requestUpdateLane(current); // 获取任务优先级
if (enableSchedulingProfiler) {
markRenderScheduled(lane);
}
const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
// ...
const update = createUpdate(eventTime, lane); // 创建更新任务
update.payload = {
element};
callback = callback === undefined ? null : callback;
if (callback !== null) {
// ...
update.callback = callback;
}
enqueueUpdate(current, update); // 将任务推入更新队列
scheduleUpdateOnFiber(current, lane, eventTime); // schedule 进行调度
return lane;
}
setState 时类组件中我们最常用的修改状态的方法,状态修改会触发更新流程。
class 组件在原型链上定义了 setState
方法,其调用了触发器 updater
上的 enqueueSetState
方法:
// packages/react/src/ReactBaseClasses.js
Component.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.',
);
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
然后我们再来看以下 updater 上定义的 enqueueSetState
方法,一看到这我们就了然了,和 updateContainer
方法中做的事情几乎一模一样,都是触发后续的更新调度。
相关参考视频讲解:进入学习
// packages/react-reconciler/src/ReactFiberClassComponent.old.js
const classComponentUpdater = {
isMounted,
enqueueSetState(inst, payload, callback) {
const fiber = getInstance(inst);
const eventTime = requestEventTime(); // 获取更新触发的时间
const lane = requestUpdateLane(fiber); // 获取任务优先级
const update = createUpdate(eventTime, lane); // 创建更新任务
update.payload = payload