主题
React 17 渲染流程
按源码调用链梳理 React 17 中
ReactDOM.render的同步渲染流程:从legacyRenderSubtreeIntoContainer到renderRootSync的 render 阶段,再到commitRoot的 commit 阶段,以及 Fiber 构建中的 reconcile 逻辑。源码基于 React 17 的实现(文件名以.old.js结尾的旧版文件)。
一、入口
创建 React 应用的入口:
tsx
ReactDOM.render(<App />, document.getElementById("root"));调用链一览:
text
ReactDOM.render
└─ legacyRenderSubtreeIntoContainer
└─ updateContainer
└─ scheduleUpdateOnFiber
└─ performSyncWorkOnRoot
├─ renderRootSync → workLoopSync → performUnitOfWork
│ ├─ beginWork(含 reconcileChildren)
│ └─ completeUnitOfWork
└─ commitRoot二、调用链
1. render
源码位置:packages/react-dom/src/client/ReactDOMLegacy.js
- 校验传入的容器
- 调用
legacyRenderSubtreeIntoContainer
js
export function render(
element: React$Element<any>,
container: Container,
callback: ?Function,
) {
invariant(
isValidContainer(container),
"Target container is not a DOM element.",
);
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
}legacyRenderSubtreeIntoContainer 即「旧版渲染子树挂载到容器上」:因此之后有文件名分为 new 和 old 的文件,都是调用 old 文件里的内容。
2. legacyRenderSubtreeIntoContainer
源码位置:packages/react-dom/src/client/ReactDOMLegacy.js
该函数分为初次加载和更新两种情况:
- 初次加载:1. 调用
legacyCreateRootFromDOMContainer生成 root 实例;2. 调用updateContainer - 更新:1. 调用
updateContainer
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) {
// Initial mount
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
fiberRoot = root._internalRoot;
// Initial mount should not be batched.
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
fiberRoot = root._internalRoot;
// Update
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(fiberRoot);
}3. updateContainer
源码位置:packages/react-reconciler/src/ReactFiberReconciler.old.js
- 创建 update 并放入队列
- 调用
scheduleUpdateOnFiber
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);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = { element };
enqueueUpdate(current, update);
scheduleUpdateOnFiber(current, lane, eventTime);
return lane;
}4. scheduleUpdateOnFiber
源码位置:packages/react-reconciler/src/ReactFiberWorkLoop.old.js
- 调用
performSyncWorkOnRoot
js
// ...
performSyncWorkOnRoot(root);5. performSyncWorkOnRoot
源码位置:packages/react-reconciler/src/ReactFiberWorkLoop.old.js
js
function performSyncWorkOnRoot(root) {
// ...
exitStatus = renderRootSync(root, lanes);
// ...
commitRoot(root);
return null;
}performSyncWorkOnRoot 就是渲染的入口:先执行 render 阶段,把 vdom 转成 fiber,然后再执行 commit,更新到 DOM。
6. renderRootSync
源码位置:packages/react-reconciler/src/ReactFiberWorkLoop.old.js
- 不断地尝试执行
workLoopSync - 如果
workLoopSync函数执行成功,则跳出循环 - 如果在执行过程中出现异常,则捕获
js
function renderRootSync(root: FiberRoot, lanes: Lanes) {
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
// 创建 workInProgress
prepareFreshStack(root, lanes);
startWorkOnPendingInteractions(root, lanes);
}
do {
try {
// 工作轮询
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
}7. workLoopSync
源码位置:packages/react-reconciler/src/ReactFiberWorkLoop.old.js
只要 workInProgress 不为空,则执行 performUnitOfWork:
js
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}8. performUnitOfWork
源码位置:packages/react-reconciler/src/ReactFiberWorkLoop.old.js
每个执行单元都有 beginWork 和 completeUnitOfWork 两个阶段:beginWork 负责将 vdom 转 fiber;completeUnitOfWork 负责处理 diff 结果。
js
function performUnitOfWork(unitOfWork: Fiber): void {
// ...
next = beginWork(current, unitOfWork, subtreeRenderLanes);
if (next === null) {
// 如果没有新的 work, 完成当前工作.
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}9. beginWork
源码位置:packages/react-reconciler/src/ReactFiberBeginWork.old.js
负责将 vdom 转 fiber,不同节点的 reconcile 逻辑不同:函数组件调用 updateFunctionComponent;class 组件调用 updateClassComponent。
js
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
// ...
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
}10. updateFunctionComponent
源码位置:packages/react-reconciler/src/ReactFiberBeginWork.old.js
函数组件会被调用,拿到 render 出的 vdom 继续进行 reconcile:
js
function updateFunctionComponent(
current,
workInProgress,
Component,
nextProps: any,
renderLanes,
) {
// 构建 hooks
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);
// 生成 Fiber 链表
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}11. reconcileChildren
源码位置:packages/react-reconciler/src/ReactFiberBeginWork.old.js
判断 current 的值,如果是第一次更新则不进行 diff:
js
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderExpirationTime: ExpirationTime,
) {
if (current === null) {
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
} else {
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderExpirationTime,
);
}
}12. ChildReconciler
源码位置:packages/react-reconciler/src/ReactChildFiber.old.js
mountChildFibers 和 reconcileChildFibers 都是 ChildReconciler 方法,只是传参不同:
js
export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);ChildReconciler 函数内部定义了一系列函数,入口函数则是 reconcileChildFibers:
js
function ChildReconciler(shouldTrackSideEffects) {
// ...
function reconcileChildFibers() {
// ...
}
return reconcileChildFibers;
}