React Fiber
背景
React 为了描述组件状态和Dom的关系,采用虚拟DOM来表示真实DOM。
每个组件在每次渲染时会创建一个新的Virtual DOM,React会把新的Virtual DOM树与现有的相比较,然后将一系列转变应用于真实DOM。
在 React 15 的版本中,虚拟 Dom 采用 Stack 架构实现,diff 更新的过程是 同步循环递归,这就导致只有所有的 组件 更新之后,才会让出主进程进行 Dom 渲染。
虚拟 Dom 树如果直接更新,当组件非常多时,给数据更新的 diff 带来了很大压力。如果 js占据主线程去做比较,渲染线程便无法做其他工作,用户的交互得不到响应,所以便出现了react fiber。
什么是 Fiber
按照名词解释,这是一种数据结构(也可叫执行单元),将虚拟 Dom Tree 转变为 可支持·断点遍历重启 的 链表(单链表树结构)。
按照动词解释,这是协程,React Fiber 把一次更新过程碎片化,细致控制虚拟 Dom 更新 和 渲染 Dom 展示这些操作的执行顺序。
基于碎片化更新的过程,每次执行完单个碎片更新,就会把主线程交给 React 其他模块,来处理其他紧急任务。如果没有紧急任务,那就继续更新。
工作原理
Fiber 节点保存了 Dom 节点,Fiber 树和 Dom 树互相呼应。
通过判断当前执行帧空闲空间,来选择执行对应优先级的执行任务,在当前帧结束前再次判断是否有剩余时间和任务来支持执行,没有则执行浏览器渲染任务,并循环这一过程来最大化提升渲染效率。
利用requestAnimationFrame
来确保每一帧执行的任务。
利用requestIdleCallback
来指定执行哪些低优先级的任务。如果遇到超过正常帧的任务,执行完毕后就需要立即归还控制权,并使用 requestIdleCallback
来再次申请下一次更新的时间片。requestIdleCallback
会给 callback
传递IdleDeadline 来判断任务的操作状态。
timeRamining()
:用来判断用户代理预计还剩余多少闲置时间。didTimeout
:用来判断当前的回调函数是否因超时而被执行。
双缓存 Fiber 树
- 基于渲染内容的 CurrentFiber Tree
- 内存中实时构建的 WorkInProgress Fiber Tree(节点之间通过 altermnate 属性连接)
根节点通过指针 current 在不同的 FiberTree 和 RootFiber 之间切换,完成 current Fiber Tree 指向的切换。
当更新内容在 WorkInProgress Fiber Tree 构建完成后,经由 Renderer 渲染到页面上后,根节点的 current 指向 WorkInProgress Fiber 树,基于互相呼应的原理,这棵 WorkInProgress Fiber Tree 此时变成了 CurrentTree。当下一次 State 更新时,构建新的 WorkInProgress Fiber Tree, 然后再次执行上述操作,便完成了 Tree 的 替换,也就是 Dom 的更新
Render 阶段
此处的 Render 并非指浏览器渲染页面,而是虚拟 Dom 和 真实 Dom 的 Diff 过程,并进行变化的内容标记收集。**Render 是一个可以被打断的过程。 ** 在该阶段构建一棵Fiber tree,产生和虚拟 Dom 一一对应的任务,并且产出最后的更新 List(Effect List)。
收集 Effect List
- 如果当前节点需要更新,则使用对应tag标记当前节点状态(props, state, context等);
- 为每个子节点创建fiber。
- 并为 需要更新的 Fiber 添加两个属性:
- firstEffect:指向第一个有变更的子fiber。
- lastEffect:指向最后一个有变更的子fiber。
- 并为 需要更新的 Fiber 添加两个属性:
而我们需要收集的就是中间nextEffect,最终形成一个单链表。
- 如果没有产生child fiber,则结束该节点,把effect list归并到return,把此节点的兄弟节点作为下一个遍历节点;否则把子节点作为下一个遍历节点;
- 如果有剩余时间,则开始下一个节点,否则等下一次主线程空闲再开始下一个节点;
- 如果没有下一个节点了,进入pendingCommit状态,此时effect list收集完毕,结束。
Commit 阶段
Commit 阶段会将根据 EffectList 将上述操作中标记需要变更的 Fiber 更新到 DOM 树上,此阶段为了确保 UI 更新的连续性,是不可中断的过程。
到这里,整个 Fiber 的 刷新和更新过程就完成了。
总结
Fiber 是基于碎片化任务和和交替变更浏览器控制权,来达到更好的更新效果。其通过双缓存机制,在其 Render 阶段比较 虚拟 Dom 树和 真实 Dom,并进行节点变更标记和收集变更依赖,在 Commit 阶段更新 被标记 Fiber 节点到真实 Dom 树完成整个更新和刷新流程。