5.3 KiB
React 从 16 开始,对底层架构做了一次重构,和 15 不同,渲染 vdom 的时候一改以往的递归执行,引入了一个新的概念:Fiber,虽然最后渲染到页面的时候仍然是递归,但是靠 Fiber 实现的递归是可中断的,根据优先级由浏览器优先执行任务,保证在大量视图需要更新的时候,浏览器仍然能保证快速的响应
在 React 中,视图的更新使用了双缓存的方式,也就是说在 React 运行时,同时有着两棵 Fiber 树,一颗是当前视图上的 Fiber 树,叫 current,另外一棵是存在内存当中的下一次视图更新时用的叫做 workInProgress,React 在构建时会创建整个 app 唯一的根 Fiber 节点,叫做 FiberRootNode,这个节点上有一个 current 指针,指向的是当前正在页面上显示的 Fiber 树也就是 current,当 workInProgress 递归生成完毕,指针会立即指向 workInProgress ,而旧的 current 则会在下一次渲染中变成 workInProgress ,就这样循环交替完成页面的递归渲染。
Fiber 递归开始首先会交由 createWorkInProgress 函数生成 WorkInProgress 的第一个 Fiber 节点,这个节点就是 FiberNode,所以我们也会从 createWorkInProgress 函数开始讲解 React 中 Fiber 递归的流程,这个函数的主要工作就是根据传入的 Fiber 节点判断复用 Fiber 节点还是创建新的 Fiber 节点,而这个判断的条件就是该 Fiber 节点的 alternate 属性是否存在。
节点的 alternate 属性不存在有两种可能:
- 首屏刷新
- 当前 Fiber 节点属性新增的节点
如果判断 alternate 不存在,会进入到 createFiber 函数:即执行创建新 Fiber 节点的逻辑。 否则就会进行 Fiber 复用。
function createWorkInProgress(current, pendingProps) {
if (workInProgress === null) {
// current 的 alternate 属性不存在会执行 createFiber 函数逻辑
// 表示当前的 Fiber 节点不存在对应的 WorkInProgress Fiber
// 在首屏渲染和新增DOM节点的情况下,alternate 是会不存在的
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode
);
// 赋值同名属性
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
// 通过 alternate 属性互相链接
workInProgress.alternate = current;
current.alternate = workInProgress;
}
......
}
这个函数具体的深入解析可以看 React 的深入探索 - createWorkInProgress。
createWorkInProgress 执行完毕之后,我们就有了一个 WorkInProgress Fiber 节点,接下来就会交给 beginWork 和 completeWork 开始正式的 Fiber 递归,在之后的 beginWork 流程中,如果发现存在新增的 Fiber 节点或是进入 Bailout 逻辑也会进入到 createWorkInProgress 这个函数逻辑中。
在 React 中 Fiber 的创建使用递归实现的深度优先遍历算法,即尽可能深的探索树的分支,探索完毕后再回溯,在这一过程中负责探索阶段的就是 beginWork 函数。
beginWork 执行在递归节点的 Fiber 创建之前,主要是为传入的 Fiber 节点创建第一个子 Fiber 节点,其内部可以看作一个大的 switch 语句,根据传入的 Fiber 节点的子元素类型不同执行不同的 Fiber 创建逻辑,不管父元素拥有多少个子元素,它最终都会创建并返回第一个子元素的 Fiber 节点,直到并不存在子元素为止,React 的深入探索 - beginWork。
如果探索到了可达的最后一个子元素,那么就会结束探索阶段,进入回溯阶段:执行completeWork 函数逻辑。
completeWork 负责深度优先遍历中的回溯阶段,它执行在递归节点的 Fiber 创建之后,主要负责完善创建好的 Fiber 节点和插入其真实 DOM 树,首屏渲染中首先进入该函数的可能是最小的不存在子元素的 Fiber 节点,也可能是只存在一个文本子元素的夫节点,因为 React 对这样的元素做了一些优化,它的子元素并不会进入 beginWork 函数逻辑,而是直接交由 beginWork 进行 Fiber 填充。
和 beginWork 中相同,completeWork 的主要逻辑也是一个巨大的 switch,根据 Fiber 节点的类型进入不同的处理逻辑 React 的深入探索 - completeWork
如果最终回溯到了 Fiber 的起始节点,那么整个首屏渲染的 Fiber 递归渲染逻辑就完成了,最后会交由 commitRoot 函数进入 commit 阶段将其渲染到页面上:React 的流程解析 - commit阶段
这里你可能会有一个疑惑,在 Fiber 递归阶段也就是 reconciler 阶段,会深度优先遍历找出所有的存在变化的 Fiber 节点,并将其打上对应的 effectTag,那么进入 commit 阶段后,也需要再次进行一次深度优先遍历找出这些存在 effectTag 的 Fiber 节点吗?
其实是不需要的,因为这样的效率太低了,在深度优先遍历的回溯阶段也就是 completeWork 的执行逻辑中,会将所有存在 effectTag 的 Fiber 节点通过单项链表的形式连接起来,这样在 commit 阶段的时候只需要遍历这一条链表就能快速的找到发生了变化的 Fiber 节点:effectList 的生成