|
|
|
>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 属性不存在有两种可能:
|
|
|
|
1. 首屏刷新
|
|
|
|
2. 当前 Fiber 节点属性新增的节点
|
|
|
|
|
|
|
|
如果判断 alternate 不存在,会进入到 createFiber 函数:即执行创建新 Fiber 节点的逻辑。
|
|
|
|
|
|
|
|
否则就会进行 Fiber 复用。
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
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 流程中,进入 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 节点:[[React 的深入探索 - effectList 链表]]
|