React Fiber
React Fiber 架构原理:关于 Fiber 树的一切 - 知乎 背景 旧 React VDom 一棵树 构建新的 vdom 通过 diff 算法比较新旧,决定渲染 Fiber 是什么? 一个树 重建了树和节点 父子双向链表链接的树 深度遍历、分割、灵活中断 Fiber实现了自己的组件调用栈,它以链表的形式遍历组件树,可以灵活的暂停、继续和丢弃执行的任务。实现方式是使用了浏览器的 requestIdleCallback 这一 API。 Fiber 树的遍历方式 实际上 Fiber 树是边构建、边遍历、边对比的,这样最大程度减少了遍历次数,也符合「可中断」的设定。 Fiber 树是深度优先遍历的。 遍历分【递】【归】两个阶段,两层循环,递(beginWork)是外层循环、归(completeUnitOfWork)是内层循环。 Fiber 树是边创建边遍历的,每个节点都经历了「创建、Diffing、收集副作用(要改哪些节点)」的过程。 其中,创建、Diffing要自上而下,因为有父才有子;收集副作用要自下而上最终收集s到根节点。 现在我们回头看遍历过程。外层循环每一步(也就是 beginWork 每次执行)都是自上而下的,并保证每个节点只走一次;内层循环每一步(在 completeUnitOfWork 里)都是自下而上的。 显然,beginWork 负责创建、Diffing,completeUnitOfWork 负责收集副作用。 树的构建 和 diffing 在React中最多会同时存在两棵Fiber树: 当前屏幕上显示内容对应的Fiber树称为 current Fiber 树 正在构建的Fiber树称为 workInProgress Fiber 树,我们这里讨论的所有遍历都在这棵树上 如果构建到一半被打断,current 树还在。如果构建并提交完成,直接把 current 树丢掉,让 workInProgress Fiber 树成为新的 current 树。所谓 Diffing 也是在这两棵树之间 diff 算法:两轮遍历 Vue 与 React diff 算法区别 effectList 副作用及其收集 在 Diffing 的过程中,我们已经注意到几次 effectTag 的标记,比如 placeChild 中的newFiber.effectTag = Placement;,deleteChild 中的 childToDelete.effectTag = Deletion;,来标记节点的创建、移动、删除。 为什么要向上收集? 给单个节点添加 effectTag 很好理解,等 Diffing 全部结束,我们统一找到有 effectTag 的节点做对应操作就好。比如某个节点标记了 Deletion,并且关联了一个 DOM 节点,就可以卸载这个 DOM 了。 从性能的角度,因为我们可以预见 Diffing 结束后要收集全部节点的 effectTag,那必然再发起一轮遍历,是不划算的,就不如在 Diffing 过程中直接同步完成收集。 还有另外一个场景:某个旧节点标记了 Deletion,但 Diffing 结束后,新树中并没有这个节点,也就不知道要删这个节点。那看起来唯一的办法就是把这个节点以某种形式挂到新节点上,但它自己又没有关联节点,就只能往上挂父节点的关联节点(这个是存在的,因为前面 Diffing 就发生在“已存在复用关系的新旧节点的子节点之间”)。 effectList 链表 Diffing 遍历过程是深度优先的,必然存在“子节点 effect 早于父节点得出”,所以在遍历离开节点时,只要不断沿着树向父节点传递,就能让每个节点收集到所有后代节点的 effect,最终传到根节点就完成了整棵树的收集。 为了让每个节点都有“保存所有后代节点 effect 的能力”,Fiber 给节点定义一个 effectList,通过链表实现。 收集过程 当某个节点不存在子节点,就要从这个节点离开了,改执行 completeUnitOfWork。遍历那块说过,completeUnitOfWork 有个内层 do while 循环,从当前节点沿着 Fiber 树往上爬。 每次循环经过一个节点,都会向上合并 effectList,又分为两部分:合并后代节点的、合并自己的。 completeUnitOfWork 的内部循环会自底向上收集 effect,不断把有 effectTag 的子节点和自身向上合并到父节点的 effectList 中,直至根节点。effectList 是个链表。



