React 更新组件其实可以分为三个阶段:
Scheduler
。任务优先级分配和调度Render
。更新组件内部状态,diff 计算 changeCommit
。应用 change,执行副作用
Scheduler
该阶段主要是给任务分配优先级,统筹任务调度
Scheduler 的源码解析可以参考:https://juejin.cn/post/6889314677528985614
react 16 基于 requestIdleCallback
的 polyfill 方案来实现任务调度,用法如下:
window.requestIdleCallback(callback[, options])
let handle = window.requestIdleCallback((idleDeadline) => {
const {
didTimeout, timeRemaining} = idleDeadline;
console.log(`是否超时?${
didTimeout}`);
console.log(`可用时间剩余${
timeRemaining.call(idleDeadline)}ms`);
// do sth
const now = Date.now(), timespent = 10;
while (Date.now() < now + timespent);
console.log(`花了${
timespent}ms`);
console.log(`可用时间剩余${
timeRemaining.call(idleDeadline)}ms`);
}, {
timeout: 1000});
// 是否超时?false
// 可用时间剩余45ms
// 花了12ms
// 可用时间剩余32ms
由于 requestIdleCallback 有兼容问题,react 团队采用的是它的 polyfill 方案,可参考
- https://www.zhuyuntao.cn/React%E4%B8%ADrequestIdleCallback%E7%9A%84polyfill%E5%AE%9E%E7%8E%B0
- https://juejin.cn/post/6861590253434585096
react18
- 更新时遍历更新每一个节点,每更新一个 Fiber 节点后,会判断累计更新时间是否超过 5ms。
- 如果超过 5ms,将下一个更新创建为一个宏任务,浏览器自动为其分配执行时机,从而不阻塞用户事件等操作。
- 如果更新的过程中,用户进行触发了点击事件,那么会在 5ms 与下一个 5ms 的间隙中去执行 click 事件回调
Render
遍历 Fiber 树,得出需要更新的节点信息。可以被打断,让位于优先级更高的操作,比如用户点击等
- 从
Fiber Root
开始遍历,构建一个新的 Fiber 树。performSyncWorkOnRoot(root)
–>renderRootSync
- 更新每个 fiber。
workLoopSync
–>performUnitOfWork
–>beginWork
function workLoopSync() {
// workInProgress:当前正在处理的节点
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork: Fiber): void {
const current = unitOfWork.alternate;
// ...
// NOTE
next = beginWork(current, unitOfWork, subtreeRenderLanes);
// ...
if (next === null) {
// 如果没有子节点,则完成当前工作
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
beginWork
主要做了以下事情:
- 判断 fiber 节点是否可以复用
- 根据不同的 Tag(标记不同的组件类型:纯组件、函数组件、类组件),生成不同的 fiber 节点赋值给
workInprogress.child
function beginWork(current, workInProgress, renderLanes) {
// ...
switch (workInProgress.tag) {
// ...
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
// NOTE
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = w