React Fiber 介绍
React Fiber 是 React 的一种重写和改进的核心算法,用于实现更细粒度的更新和高效的调度。它是 React 16 版本中的一个重要更新,使得 React 能够更好地处理复杂和高频的用户交互。以下是对 React Fiber 的详细介绍:
为什么需要 React Fiber?
在传统的 React 中,更新操作是同步的,一旦开始更新,整个组件树的更新过程不会被中断。这在处理复杂组件树或高频用户交互时,可能导致界面卡顿和响应变慢。为了解决这个问题,React Fiber 引入了更灵活的调度机制,使得 React 能够将更新任务分成更小的工作单元,并在适当的时候中断和恢复这些任务,从而提升应用的性能和用户体验。
Fiber 架构具有以下特点:
增量渲染
Fiber 将渲染工作分成多个小的工作单元,这些单元可以在多个帧中执行。由于分成了更小的任务单元,在这些任务单元之间可以停顿,可以执行其他任务。
优先级调度
不同的任务根据其重要性被赋予不同的优先级,确保用户输入等高优先级任务能够快速响应。
恢复和暂停
React 可以在处理完一个工作单元后中断,检查是否有更高优先级的任务需要处理。如果有,则优先处理高优先级任务。否则,继续处理剩余的渲染任务。
两阶段 & 两棵树
Fiber 架构包含两个阶段:
调度阶段(Reconciliation Phase)
这一阶段,React 会计算需要更新的组件和对应的状态变更。调度阶段是可以被中断的,React 会根据优先级逐步处理更新任务。
提交阶段(Commit Phase)
一旦调度阶段完成,提交阶段会将更新应用到实际的 DOM 中。提交阶段是同步的,React 会确保所有的 DOM 变更在一次帧内完成。
Fiber架构在内存中包含了两棵树,一棵是当前树已经渲染完成的树,也就是已经展现在页面上的。一棵是工作树(Work-in-Progress Tree),这两棵树互相替换进行状态的更新。
克隆和更新:
当发生更新(例如,状态变化或新的 props)时,React 通过克隆当前树创建一棵进行中工作树。当前树中的每个节点都会被复制到进行中工作树中。
然后,React 在工作树上执行差异分析,这涉及调用组件的 render 方法,将生成的元素与以前的元素进行比较,并在需要时生成新的进行中工作 fiber。
提交阶段:
一旦进行中工作树完全调和并计算了所有更新,React 进入提交阶段。在提交阶段,工作树中的更改会应用到 DOM 中,工作树成为新的当前树。这是一个原子操作,确保 UI 一致地更新。
双缓冲:
React Fiber 使用类似于计算机图形学中的双缓冲的概念。通过在单独的副本(工作树)上工作,React 可以在屏幕外计算更新,然后快速将其应用到屏幕上,最大限度地减少 UI 处于不一致状态的时间。
Fiber 节点对象
Fiber节点主要包含以下属性,完整代码,移步至 React 源码,Fiber节点是 React 的核心,Fiber 节点保存着和节点相关的所有重要信息。不同 ReactElement,每次都创建新的,Fiber 节点是从用的这也就是为什么函数式组件可以有状态的原因。
{
// 组件或 DOM 节点的类型(例如:"div", "App", "Button")
type: 'div',
// 关联的 DOM 节点(如果适用)
stateNode: document.createElement('div'),
// 当前 fiber(表示该组件的最新 fiber)
// 用于在下一个调和阶段检查此 fiber 是否已更新
return: null,
child: null,
sibling: null,
index: 0,
// 备用 fiber(表示该组件的上一个 fiber)
// 用于在下一个提交阶段对 DOM 执行副作用和清理操作
// 这仅用于非并发模式
alternate: null,
// 备忘的 props 和 state(该组件的最新 props 和 state)
// 用于在下一个调和阶段检查组件是否需要更新
memoizedProps: {},
memoizedState: {},
// 过期时间(该组件需要更新的截止时间)
// 用于在下一个阶段优先更新哪些组件
expirationTime: 0,
}
工作的单元划分
在 Fiber中,任务被划分成了工作单元,根据优先级进行调度,优先级高的先执行。Fiber中,通过 WorkLoop 执行 Fiber树上的节点,每次执行前会检查是否需要释放给更高优先级的任务,相当于暂停的操作。WokLoop 源代码
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
// $FlowFixMe[incompatible-call] found when upgrading Flow
performUnitOfWork(workInProgress);
}
}
执行任务,任务具体的执行是在 beginWork 和 completeUnitOfWork 中,beginWork中处理 Fiber节点的主要逻辑,completeUnitOfWork主要是收尾工作。
function performUnitOfWork(unitOfWork: Fiber): void {
// The current, flushed, state of this fiber is the alternate. Ideally
// nothing should rely on this, but relying on it here means that we don't
// need an additional field on the work in progress.
const current = unitOfWork.alternate;
setCurrentDebugFiberInDEV(unitOfWork);
let next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork(current, unitOfWork, renderLanes);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork(current, unitOfWork, renderLanes);
}
resetCurrentDebugFiberInDEV();
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
总结
React Fiber 是 React 渲染的重要概念,可以说 React 主要数据都是保存在 Fiber 树上,对状态的改动最终都会体现在 Fiber 节点的更新上。随着前端页面的复杂度的升高,对于渲染效率的要求也越来越高,Fiber 这种将渲染任务进行切分的设计理念大大提高了复杂前端单页面应用的用户体验。