可以理解成 fiber 就是 React 的虚拟 DOM 。
为什么要用fiber?
在 Reactv15 以及之前的版本,React 对于虚拟 DOM 是采用递归方式遍历更新的,比如一次更新,就会从应用根部递归更新,递归一旦开始,中途无法中断,随着项目越来越复杂,层级越来越深,导致更新的时间越来越长,给前端交互上的体验就是卡顿。
Reactv16 为了解决卡顿问题引入了 fiber ,为什么它能解决卡顿,更新 fiber 的过程叫做 Reconciler(调和器),每一个 fiber 都可以作为一个执行单元来处理,所以每一个 fiber 可以根据自身的过期时间expirationTime( v17 版本叫做优先级 lane )来判断是否还有空间时间执行更新,如果没有时间更新,就要把主动权交给浏览器去渲染,做一些动画,重排( reflow ),重绘 repaints 之类的事情,这样就能给用户感觉不是很卡。然后等浏览器空余时间,在通过 scheduler (调度器),再次恢复执行单元上来,这样就能本质上中断了渲染,提高了用户体验。
React.element ,fiber 和真实 DOM 三者是什么关系?
- element 是 React 视图层在代码层级上的表象,也就是 jsx 。
- DOM 是元素在浏览器上给用户直观的表象。
- fiber 可以说是是 element 和真实 DOM 之间的交流枢纽站,一方面每一个类型 element 都会有一个与之对应的 fiber 类型,element 变化引起更新流程都是通过 fiber 层面做一次调和改变,然后对于元素,形成新的 DOM 做视图渲染。
双缓冲树
canvas 绘制动画的时候,如果上一帧计算量比较大,导致清除上一帧画面到绘制当前帧画面之间有较长间隙,就会出现白屏。为了解决这个问题,canvas 在内存中绘制当前动画,绘制完毕后直接用当前帧替换上一帧画面,由于省去了两帧替换间的计算时间,不会出现从白屏到出现画面的闪烁情况。这种在内存中构建并直接替换的技术叫做双缓存。
React 用 workInProgress 树(内存中构建的树) 和 current (渲染树) 来实现更新逻辑。双缓存一个在内存中构建,一个渲染视图,两颗树用 alternate 指针相互指向,在下一次渲染的时候,直接复用缓存树做为下一次渲染树,上一次的渲染树又作为缓存树,这样可以防止只用一颗树更新状态的丢失的情况,又加快了 DOM 节点的替换与更新。
render 阶段
-
beginWork:是向下调和的过程。就是由 fiberRoot 按照 child 指针逐层向下调和,期间会执行函数组件,实例类组件,diff 调和子节点,打不同effectTag。
- 对于组件,执行部分生命周期,执行 render ,得到最新的 children 。
- 向下遍历调和 children ,复用 oldFiber ( diff 算法),diff 流程在第十二章已经讲过了。
- 打不同的副作用标签 effectTag ,比如类组件的生命周期,或者元素的增加,删除,更新。
-
completeUnitOfWork:是向上归并的过程
- 首先 completeUnitOfWork 会将 effectTag 的 Fiber 节点会被保存在一条被称为 effectList 的单向链表中。在 commit 阶段,将不再需要遍历每一个 fiber ,只需要执行更新 effectList 就可以了。
- completeWork 阶段对于组件处理 context ;对于元素标签初始化,会创建真实 DOM ,将子孙 DOM 节点插入刚生成的 DOM 节点中;会触发 diffProperties 处理 props ,比如事件收集,style,className 处理。
commit 阶段
commit 阶段做的事情是:
- 一方面是对一些生命周期和副作用钩子的处理,比如 componentDidMount ,函数组件的 useEffect ,useLayoutEffect ;
- 另一方面就是在一次更新中,添加节点(
Placement),更新节点(Update),删除节点(Deletion),还有就是一些细节的处理,比如 ref 的处理。
细分:
- Before mutation 阶段(执行 DOM 操作前);
- mutation 阶段(执行 DOM 操作);
- layout 阶段(执行 DOM 操作后);
再具体:
-
Before mutation 阶段(执行DOM操作前):
- 如果是类组件有
getSnapshotBeforeUpdate,那么会执行这个生命周期,获取 DOM 快照 - 会异步调用 useEffect(防止同步执行时阻塞浏览器做视图渲染)
- 如果是类组件有
-
mutation 阶段(执行 DOM 操作);
- 置空 ref ,在 ref 章节讲到对于 ref 的处理。
- 对新增元素,更新元素,删除元素。进行真实的 DOM 操作。
-
layout 阶段(执行 DOM 操作后)
- commitLayoutEffectOnFiber 对于类组件,会执行生命周期,setState 的callback,对于函数组件会执行 useLayoutEffect 钩子。
- 如果有 ref ,会重新赋值 ref 。
useLayoutEffect的说明