Fiber
Fiber是React 16引入的重新实现的协调算法,目的是解决大型应用中的卡顿问题,实现增量渲染和任务调度。而diff算法是React在更新时比较新旧虚拟DOM树,找出差异并高效更新的方法.
什么是Fiber?
React Fiber 是 React 16 中引入的一种新的渲染架构,它重新实现了调度算法和渲染流程。它将原来的同步渲染方式改为异步渲染,使得 React 应用能够更好地控制任务的优先级,实现更流畅的用户界面。
为什么需要 Fiber?
为了解决长时间任务的中断问题,以改善用户界面的响应和性能。在传统的栈调用中,如果存在一个长时间的渲染任务,将会阻塞浏览器的主线程,导致用户无法与应用进行交互。Fiber 的目标是实现可中断和可恢复的渲染过程,使得 React 应用可以更好地响应用户的操作,并能够优先处理重要的任务。旨在优化渲染过程、实现异步渲染,并提高应用的性能和用户体验。
Fiber的结构
Fiber既可以看作是链表,也可以看作是树。说他是树,是因为它的形状像树,但是并没有树的特征,树是一个只有指向子节点的指针,并没有指向父节点的指针。每个 Virtual DOM 都可以表示为一个 Fiber,一个 Fiber 包括了 child第一个子节点、sibling兄弟节点、return父节点等属性,React Fiber 机制的实现,就是依赖于以下的数据结构。每个 fiber 树它可以由多个子 fiber 组成:
// Fiber 节点的核心结构
{
tag: FunctionComponent | ClassComponent | HostComponent,
type: 'div' | YourComponent,
stateNode: DOM节点或组件实例,
return: 父Fiber,
child: 第一个子Fiber,
sibling: 兄弟Fiber,
memoizedState: Hooks链表(用于函数组件),
effectTag: 标记需要进行的操作(如插入、更新、删除),
// ...其他字段
}
Fiber 的工作原理
React Fiber 的工作原理包括以下几个关键步骤:
任务调度:React Fiber 使用一个双缓冲技术,构建一个可中断的任务链表,每个任务单元称为一个 Fiber 节点,通过 diff 算法对 Fiber 树进行优化识别哪些节点需要更新。
优先级调度:每个 Fiber 节点都与一个优先级相关联。调度器根据任务的优先级决定任务执行的先后顺序。具有较高优先级的任务会打断正在执行的低优先级任务。
增量渲染:为了提高渲染的平滑度,React Fiber 将渲染任务拆分为多个小任务,在每个任务之间释放控制权给浏览器,从而避免了长时间的 JavaScript 执行阻塞。
Fiber 的工作流程
渲染有两个阶段:Reconciliation(协调阶段) 和 Commit(提交阶段).
协调阶段: 可以认为是 Diff 阶段, 这个阶段可以被中断, 这个阶段会找出所有节点变更,例如节点新增、删除、属性变更等等, 这些变更React 称之为'副作用(Effect)'
提交阶段: 将上一个阶段计算出来的需要处理的**副作用(Effects)**一次性执行了。这个阶段必须同步执行,不能被打断。
协调阶段如果时间片用完,React就会选择让出控制权。因为协调阶段执行的工作不会导致任何用户可见的变更,所以在这个阶段让出控制权不会有什么问题。
需要注意的是:因为协调阶段可能被中断、恢复,甚至重做,React 协调阶段的生命周期钩子可能会被调用多次!, 例如 componentWillMount 可能会被调用两次。因此建议 协调阶段的生命周期钩子不要包含副作用. 索性 React 就废弃了这部分可能包含副作用的生命周期方法,例如componentWillMount、componentWillUpdate.
调度阶段:
可中断:React 遍历 Fiber 树,对比新旧节点,标记变更(如 Placement, Update, Deletion)。
优先级控制:高优先级任务(如用户输入)可打断低优先级任务(如渲染大量数据)。
提交阶段:
不可中断:将标记的变更一次性提交到 DOM,保证 UI 一致性。
执行副作用:调用生命周期方法、Hooks 的清理/执行函数。
Fiber的实现
主要是通过两个原生的 API 来实现的 requestAnimationFrame 和 requestIdleCallback
显示器每秒 60 帧我们看着才不会感觉到卡,比如动画的时候,一帧的时间内布局和绘制结束,还有剩余时间,JS 就会拿到主线程使用权,如果 JS 某个任务执行过长,动画下一帧开始时 JS 还没有执行完,就会导致掉帧,出现卡顿。
通过把 JS 任务分成更小的任务块,分到每一帧上的方式,一帧时间到先暂停 JS 执行,然后下一帧绘制任完成再把主线程交给 JS,在每一帧绘制之前调用 requestAnimationFrame;在每一帧空间阶段,就是一帧动画任务完成,下一帧还没到开始时间,这中间还有时间的话就调用 requetIdleCallback,执行它里面的任务。
requestAnimationFrame(RAF):
RAF是一个浏览器提供的API,用于在下一次浏览器渲染帧之前执行回调函数。通常用于执行与动画和绘制相关的任务。
在React Fiber中,RAF被用于分割渲染任务,将任务拆分成多个小的任务单位,使其分布在多个刷新周期内执行。这样做的好处是避免长时间的任务执行,使得用户界面保持响应,并能够在每个浏览器帧之间进行任务切换。
requestIdleCallback(RIC):
RIC是一个浏览器提供的API,用于在浏览器空闲时执行回调函数。它会在浏览器处理其他高优先级任务后才触发回调,以确保不会影响用户体验。
在React Fiber中,RIC被用于执行渲染任务的优先级控制和调度。React将任务分为不同的优先级,并在浏览器空闲时调用RIC来执行具有不同优先级的任务。这种方式可以确保高优先级任务(如用户交互)得到及时响应,而低优先级任务(如异步渲染)会在浏览器空闲时才执行。
核心思想
Fiber也称协程或者纤程。它和线程并不一样,协程本身是没有并发或者并行能力的(需要配合线程),它只是一种控制流程的让出机制。让出 CPU 的执行权,让 CPU 能在这段时间执行其他的操作。渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。
React在渲染时,会递归对比V-Dom树,找出需要变动的节点,同步更新他们。在这个执行过程中,React会占据浏览器资源,用户触发的事件得不到相应,并会掉帧,从而导致用户感觉页面卡顿。
为了给用户制造一种页面很流畅的假象,不能让一个任务长期占用资源,就需要通过某些调度策略合理的分配CPU资源,从而提高浏览器的响应速率,同时兼顾任务的执行效率。所以React通过Fiber架构,让这个执行过程变成可被中断,及时的让出CPU的控制权,让浏览器更好的响应用户的交互。
1.分批延时对DOM进行操作,避免一次性操作大量 DOM 节点,可以得到更好的用户体验;
2.渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。
Diff 算法
React 的 Diff 算法通过以下策略
1.同层比较:只比较同一层级的节点,不跨层级移动(减少复杂度)。
2.类型不同则销毁重建:若节点类型不同(如 div → span),直接销毁整个子树。
3.Key 优化列表对比:对列表元素使用唯一且稳定的 key,识别元素是否移动。
React 的 diff 算法是分成两次遍历的
第一轮遍历,对比vdom和老的Fiber,可以复用就处理下一个节点,否则就结束遍历。
如果所有的新的vdom处理完了,那就把剩下的老Fiber节点删掉就行。如果还有vdom没处理,那就进行第二次遍历:
第二轮遍历,把剩下的老Fiber放到map里,遍历剩下的vdom,从map里查找,如果找到了,就移动过来。第二轮遍历完了之后,把剩余的老Fiber删掉,剩余的vdom新增。
Diff 算法与 Fiber 的协作
Fiber 树对比:在 Render Phase,Fiber 节点通过 Diff 算法标记变更。
复用 Fiber 节点:若组件类型和 key 相同,复用现有 Fiber 节点,减少内存分配。
diff优化技巧
1.避免不必要的渲染
React.memo / PureComponent:缓存组件,避免无意义的子组件重渲染。
useMemo / useCallback:缓存计算结果和函数,减少子组件的依赖变化。
2.合理使用 Key
唯一性:key 应稳定唯一(避免使用数组索引,尤其在动态列表)。
稳定性:避免随机数(如 Math.random()),导致 key 变化引发重建。
3.减少 Diff 范围
组件拆分:将频繁变动的部分拆分为小组件,限制 Diff 范围。
避免深层嵌套:减少树的层级,提升 Diff 效率。
为什么列表需要 key?
key 是 React 中用于标识节点的唯一性的一种机制。在 Diff 算法中,React 使用 key属性来快速定位到需要更新的节点,从而提高 Diff 算法的性能。
无 key:React 无法识别元素是否移动,可能导致性能下降或状态错乱(如表单输入框)。
有 key:帮助 React 准确识别元素,复用 DOM 节点,提高性能。
Fiber 如何支持并发模式?
时间切片(Time Slicing):将渲染任务拆分为 5ms 的小块,通过 requestIdleCallback 在浏览器空闲时执行。
优先级调度:通过requestIdleCallback执行高优先级任务(如用户输入)可中断低优先级任务(如渲染)。