背景
react 架构局限性
v16 采用 Stack Reconciliation,即递归的方式处理组件树更新,从根节点开始遍历整棵树,伴随着同步、阻塞,会出现一些问题
- 渲染时间过长: 复杂度过高的任务导致主线程阻塞,Native GUI 线程中断,无法响应用户指令,影响体验
- 优先级管理: 无优先级概念,导致关键任务可能被低优先级任务阻塞
- 中断恢复机制: 一旦开始就无法中断和恢复
fiber 解决方案
- 性能提升:
- 增量渲染: 将整个渲染过程分成多个可中断的、执行时间短的小任务,并使用任务调度器(Scheduler)来动态地调度这些任务,避免长时间的阻塞,提升了渲染的灵活性和效率
- 任务分片: Time Slicing,fiber 可在空闲时间内执行渲染任务,从而最大限度地利用浏览器的空闲时间
- 任务队列 (Task Queue): 将所有的更新操作封装成任务,并放入一个任务队列中
- 执行任务: 执行队列任务,记录当前任务执行时间
- 时间检查: 若任务执行时间超过阈值 (默认 5 毫秒),则中断执行
- 让出线程: 使用
MessageChannel
和 postMessage
让出线程,浏览器执行其他任务
- 通知执行: 浏览器执行完成后,通过
onmessage
事件继续执行之前中断的任务
- 循环执行: 重复此过程直到任务队列清空
- 用户体验:
- 分配优先级: 确保高优先级的任务尽快处理
- 同步任务: 最高优先级的任务,通常用于处理用户交互事件和页面加载过程中的同步操作
- 异步任务: 中等优先级的任务,包括普通的更新任务和网络请求等异步操作
- 空闲任务: 最低优先级的任务,通常用于执行一些不紧急的任务,如日志记录或统计信息收集等
1 2 3 4 5 6
| export const NoPriority = 0; export const ImmediatePriority = 1; export const UserBlockingPriority = 2; export const NormalPriority = 3; export const LowPriority = 4; export const IdlePriority = 5;
|
- 灵活和扩展:
- 为未来特性提供基础,例如 Concurrent Mode 和 Suspense
- 维护和调试:
引入
浏览器帧
- 浏览器对每一帧的执行和渲染的流程分为 7 个阶段

- 第 1-3 阶段: js 执行阶段,分别为用户事件回调、定时器回调、窗口变更事件回调
- 第 4 阶段: rAF 阶段,即 window.requestAnimationFrame 回调执行阶段
- 第 5-6 阶段: 页面渲染阶段,前 3 阶段 js 执行时间过长将阻塞渲染,导致页面卡顿
- 第 7 阶段: 帧空闲阶段,即 window.requestIdleCallback 回调执行阶段。若前 6 阶段运行时间超过 16.6ms,则该回调不会执行
requestIdleCallback: 回调会传入一个期限,表示浏览器有多少时间供事件执行, 为了不耽误事,最好在这个时间范围内执行完毕。requestAnimationFrame 的回调会在每一帧确认执行, 属于高优先级任务,而 requestIdleCallback 的回调不一定, 属于低优先级任务
任务优先级
- Immediate(-1): 任务会同步执行,或者说要马上执行且不能中断
- UserBlocking(250ms): 任务一般是用户交互的结果,需要即时得到反馈
- Normal(5s): 不需要立即完成的任务,例如网络请求
- Low(10s): 任务可以放后,但是最终应该得到执行,例如分析通知
- Idle(没有超时时间): 一些没有必要做的任务
fiber 结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| function FiberNode( this: $FlowFixMe, tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode ) { this.tag = tag; this.key = key; this.elementType = null; this.type = null; this.stateNode = null;
this.return = null; this.child = null; this.sibling = null; this.index = 0;
this.ref = null; this.refCleanup = null;
this.pendingProps = pendingProps; this.memoizedProps = null; this.updateQueue = null; this.memoizedState = null; this.dependencies = null;
this.mode = mode;
this.flags = NoFlags; this.subtreeFlags = NoFlags; this.deletions = null;
this.lanes = NoLanes; this.childLanes = NoLanes;
this.alternate = null;
if (enableProfilerTimer) { }
if (__DEV__) { } }
|
Concurrent Mode
渲染模式,提高性能和体验,利用了 fiber 架构的增量渲染和时间切片技术,能够在多个优先级任务之间动态地调度执行,使得高优先级任务能够优先得到处理。具体有以下实现
batchedUpdates
连续触发多次状态更新会被更智能地合并为单一的更新操作,以避免不必要的渲染
- 早期版本的 batchedUpdates 无法合并一些脱离当前上下文环境的更新
- 在 Concurrent Mode 模式下,状态更新的合并不再局限于当前上下文,而是根据更新的优先级来决定是否合并
Suspense
用于在组件树中等待异步加载内容的机制,能够在数据加载完成之前显示占位符或 loading 界面,提高用户体验
Lazy Loading
延迟加载组件或资源,能够提高页面的加载速度和响应速度,减少初次加载时的资源占用和等待时间
useDeferredValue
内部会调用 useState 并触发一次更新,但优先级很低,当前如果有正在进行中的更新,不会受 useDeferredValue 产生的更新影响,能够返回一个延后更新的值
1
| const deferredValue = useDeferredValue(value);
|
优雅降级
用于处理低优先级任务无法立即执行的情况。在任务无法立即执行时,Concurrent Mode 能够自动调整任务的优先级,保证高优先级任务能够得到及时处理,避免页面加载和渲染的阻塞
工作原理
工作单元: 每个 fiber 节点代表一个单元,所有 fiber 节点共同组成一个 fiber 链表树,精确定位控制节点行为
链表属性: child、sibling 和 return 字段形成节点关系网
双缓冲:
- 两棵树,当前树 (currentFiber),基于此创建的临时树 (workInProgressFiber),WIP 包含了当前更新受影响的顶层节点直至其所有后代节点
- WIP 在后台进行比较更新,完成后复制添加其他未更新节点,最终替换 currentFiber,成为新的 currentFiber
- 两棵树可随时进行比较、中断、恢复等操作,提升渲染性能及 UI 稳定
更新判断: 通过 memoizedProps、pendingProps 和 memoizedState 字段计算该节点上一个状态和即将应用的状态。从而判断是否需要更新,避免不必要的渲染
副作用收集: flags、subtreeFlags 字段标识 fiber 及其子树中需要执行的副作用,React 会收集并在 commit 阶段一次性执行
工作流程
Reconciliation
构建工作数,并通过协调算法比较新旧 props 确定更新的节点
- 旧的协调算法: 深度优先遍历 VDOM 树,一旦开始便无法中断
- 新的协调算法: 增量渲染、时间切片
- 协调阶段可能被中断、恢复,甚至重做,协调阶段的生命周期钩子可能会被调用多次,例如 componentWillMount 可能会被调用两次,所以 v17 后一些生命周期废除了
beginWork
第一阶段: 创建与标记更新节点 (ReactFiberBeginWork.js)
- 判断 Fiber 节点是否要更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| function beginWork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes ): Fiber | null { if (current !== null) { const oldProps = current.memoizedProps; const newProps = workInProgress.pendingProps;
if (oldProps !== newProps || hasLegacyContextChanged()) { didReceiveUpdate = true; } else { } } else { didReceiveUpdate = false; }
workInProgress.lanes = NoLanes;
switch (workInProgress.tag) { case IndeterminateComponent: case LazyComponent: case FunctionComponent: case ClassComponent:
} }
|
- 判断 Fiber 子节点是更新还是复用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| export function reconcileChildren( current: Fiber | null, workInProgress: Fiber, nextChildren: any, renderLanes: Lanes ) { if (current === null) { workInProgress.child = mountChildFibers( workInProgress, null, nextChildren, renderLanes ); } else { workInProgress.child = reconcileChildFibers( workInProgress, current.child, nextChildren, renderLanes ); } }
export const reconcileChildFibers: ChildReconciler = createChildReconciler(true); export const mountChildFibers: ChildReconciler = createChildReconciler(false);
|
completeUnitOfWork
第二阶段: 遍历 Fiber 节点,记录有副作用节点的关系,收集副作用列表 (ReactFiberWorkLoop.js)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| function completeUnitOfWork(unitOfWork: Fiber): void { let completedWork: Fiber = unitOfWork; do { const current = completedWork.alternate; const returnFiber = completedWork.return;
let next; next = completeWork(current, completedWork, renderLanes);
if (next !== null) { workInProgress = next; return; } const siblingFiber = completedWork.sibling; if (siblingFiber !== null) { workInProgress = siblingFiber; return; } completedWork = returnFiber; workInProgress = completedWork; } while (completedWork !== null);
if (workInProgressRootExitStatus === RootInProgress) { workInProgressRootExitStatus = RootCompleted; } }
|
completeWork
第三阶段: 据 tag 进行不同的处理 (ReactFiberCompleteWork.js)
bubbleProperties: 记录 Fiber 的副作用标志,并为子 Fiber 创建链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| function completeWork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes ): Fiber | null { const newProps = workInProgress.pendingProps; switch (workInProgress.tag) { case FunctionComponent: case ForwardRef: case SimpleMemoComponent: bubbleProperties(workInProgress); return null; case ClassComponent: bubbleProperties(workInProgress); return null; case HostComponent: return null; } }
function bubbleProperties(completedWork: Fiber) { const didBailout = completedWork.alternate !== null && completedWork.alternate.child === completedWork.child;
let newChildLanes = NoLanes; let subtreeFlags = NoFlags;
if (!didBailout) { let child = completedWork.child; while (child !== null) { newChildLanes = mergeLanes( newChildLanes, mergeLanes(child.lanes, child.childLanes) );
subtreeFlags |= child.subtreeFlags; subtreeFlags |= child.flags;
child.return = completedWork; child = child.sibling; } completedWork.subtreeFlags |= subtreeFlags; } else { let child = completedWork.child; while (child !== null) { newChildLanes = mergeLanes( newChildLanes, mergeLanes(child.lanes, child.childLanes) );
subtreeFlags |= child.subtreeFlags & StaticMask; subtreeFlags |= child.flags & StaticMask;
child.return = completedWork; child = child.sibling; } completedWork.subtreeFlags |= subtreeFlags; } completedWork.childLanes = newChildLanes;
return didBailout; }
|
Fiber 架构计算速度加快
flags 或 subtreeFlags 是 16 进制的标识,进行按位或(|)运算后,可以记录当前节点本身和子树的副作用类型,通过运算结果可以减少节点的遍历
调和过程可中断
Concurrent Mode 的能力使得 React 可以优先处理高优先级的更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| function renderRootConcurrent(root: FiberRoot, lanes: Lanes) { const prevExecutionContext = executionContext; executionContext |= RenderContext; const prevDispatcher = pushDispatcher(root.containerInfo); const prevCacheDispatcher = pushCacheDispatcher();
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) { }
outer: do { try { if ( workInProgressSuspendedReason !== NotSuspended && workInProgress !== null ) { const unitOfWork = workInProgress; const thrownValue = workInProgressThrownValue;
resumeOrUnwind: switch (workInProgressSuspendedReason) { case SuspendedOnError: { break; } case SuspendedOnData: { break outer; } case SuspendedOnInstance: { workInProgressSuspendedReason = SuspendedOnInstanceAndReadyToContinue; break outer; } case SuspendedAndReadyToContinue: { if (isThenableResolved(thenable)) { workInProgressSuspendedReason = NotSuspended; workInProgressThrownValue = null; replaySuspendedUnitOfWork(unitOfWork); } else { workInProgressSuspendedReason = NotSuspended; workInProgressThrownValue = null; throwAndUnwindWorkLoop(unitOfWork, thrownValue); } break; } case SuspendedOnInstanceAndReadyToContinue: { const isReady = preloadInstance(type, props); if (isReady) { workInProgressSuspendedReason = NotSuspended; workInProgressThrownValue = null; const sibling = hostFiber.sibling; if (sibling !== null) { workInProgress = sibling; } else { const returnFiber = hostFiber.return; if (returnFiber !== null) { workInProgress = returnFiber; completeUnitOfWork(returnFiber); } else { workInProgress = null; } } break resumeOrUnwind; } } } }
workLoopConcurrent(); break; } catch (thrownValue) { handleThrow(root, thrownValue); } } while (true);
resetContextDependencies(); popDispatcher(prevDispatcher); popCacheDispatcher(prevCacheDispatcher); executionContext = prevExecutionContext;
if (workInProgress !== null) { return RootInProgress; } else { workInProgressRoot = null; workInProgressRootRenderLanes = NoLanes; finishQueueingConcurrentUpdates(); return workInProgressRootExitStatus; } }
|
Commit
通过 commitRoot 和 commitRootImpl 方法,遍历在 Reconciliation 阶段创建的副作用列表进行更新,更新 DOM 并执行副作用。一旦进入提交阶段,需要正确地处理各种副作用,无法中断
BeforeMutation
第一阶段: 遍历副作用列表 (ReactFiberCommitWork.js)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| export function commitBeforeMutationEffects( root: FiberRoot, firstChild: Fiber ): boolean { nextEffect = firstChild; commitBeforeMutationEffects_begin();
const shouldFire = shouldFireAfterActiveInstanceBlur; shouldFireAfterActiveInstanceBlur = false; focusedInstanceHandle = null;
return shouldFire; }
|
CommitMutation
第二阶段: 提交更新 (ReactFiberCommitWork.js)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| export function commitMutationEffects( root: FiberRoot, finishedWork: Fiber, committedLanes: Lanes ) { inProgressLanes = committedLanes; inProgressRoot = root;
commitMutationEffectsOnFiber(finishedWork, root, committedLanes);
inProgressLanes = null; inProgressRoot = null; }
|
commitLayout
第二阶段: 处理 layout effects (ReactFiberCommitWork.js)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| export function commitLayoutEffects( finishedWork: Fiber, root: FiberRoot, committedLanes: Lanes ): void { inProgressLanes = committedLanes; inProgressRoot = root;
const current = finishedWork.alternate; commitLayoutEffectOnFiber(root, current, finishedWork, committedLanes);
inProgressLanes = null; inProgressRoot = null; }
|