原文地址
React 16 之前和之后最大的区别就是 16 引入了 fiber,又基于 fiber 实现了 hooks。整天都提 fiber,那 fiber 到底是啥?它和 vdom 是什么关系?
与其看各种解释,不如手写一个 fiber 版 React,当你能实现的时候,一定是彻底理解了。

vdom 和 fiber

首先,我们用 vdom 来描述界面结构,比如这样:

  1. {
  2. "type": "ul",
  3. "props": {
  4. "className": "list",
  5. "children": [
  6. {
  7. "type": "li",
  8. "props": {
  9. "className": "item",
  10. "children": [
  11. "aa"
  12. ]
  13. }
  14. },
  15. {
  16. "type": "li",
  17. "props": {
  18. "className": "item",
  19. "children": [
  20. "bb"
  21. ]
  22. }
  23. }
  24. ]
  25. }
  26. }

这很明显就是一个 ul、li 的结构。但是我们不会直接手写 vdom,而是会用 jsx:

  1. const data = {
  2. item1: 'bb',
  3. item2: 'cc'
  4. }
  5. const jsx = <ul className="list">
  6. <li className="item" style={{ background: 'blue', color: 'pink' }}
  7. onClick={() => alert(2)}>aa</li>
  8. <li className="item">{data.item1}<i>xxx</i></li>
  9. <li className="item">{data.item2}</li>
  10. </ul>;

jsx 使用 babel 编译,我们配置一下 .babelrc.js:

  1. module.exports = {
  2. presets: [
  3. [
  4. '@babel/preset-react',
  5. {
  6. pragma: 'Dong.createElement'
  7. }
  8. ]
  9. ]
  10. }

然后用 babel 编译它:

  1. babel index.js -d ./dist

编译结果是这样的:

  1. const data = {
  2. item1: 'bb',
  3. item2: 'cc'
  4. };
  5. const jsx = Dong.createElement("ul", {
  6. className: "list"
  7. }, Dong.createElement("li", {
  8. className: "item",
  9. style: {
  10. background: 'blue',
  11. color: 'pink'
  12. },
  13. onClick: () => alert(2)
  14. }, "aa"), Dong.createElement("li", {
  15. className: "item"
  16. }, data.item1, Dong.createElement("i", null, "xxx")), Dong.createElement("li", {
  17. className: "item"
  18. }, data.item2));

这里的 createElement 就叫做 render function,它的执行结果是 vdom。
为什么不直接把 jsx 编译为 vdom 呢?
因为 render function 可以执行动态逻辑呀。我们可以加入 state、props,也可以包装一下实现组件
2022/04/12 【手写简易版 React 来彻底搞懂 fiber 架构】 - 图1
这样,我们只要实现 Dong.createElement 就能拿到 vdom 了:
createElement 就是返回 type、props、children 的对象。
我们把 children 也放在 props 里,并且文本节点单独创建:

  1. function createElement(type, props, ...children) {
  2. return {
  3. type,
  4. props: {
  5. ...props,
  6. children: children.map(child =>
  7. typeof child === "object"
  8. ? child
  9. : createTextElement(child)
  10. ),
  11. }
  12. }
  13. }
  14. function createTextElement(text) {
  15. return {
  16. type: "TEXT_ELEMENT",
  17. props: {
  18. nodeValue: text,
  19. children: [],
  20. },
  21. }
  22. }
  23. const Dong = {
  24. createElement
  25. }

这样执行以后渲染出来的就是 vdom:
我打印了一下:
2022/04/12 【手写简易版 React 来彻底搞懂 fiber 架构】 - 图2
接下来递归渲染这棵 vdom 不就是渲染么,也就是通过 document.createElement 创建元素、设置属性、样式、事件监听器等。
等等,如果这样做,那就是 React 16 之前的架构了。这个我们实现过:
手写简易前端框架:vdom 渲染和 jsx 编译
手写简易前端框架:function 和 class 组件
手写简易前端框架:vdom 渲染和 jsx 编译

React 16 之后引入了 fiber 架构,就是在这里做了改变,它不是直接渲染 vdom 了,而是先转成 fiber:
2022/04/12 【手写简易版 React 来彻底搞懂 fiber 架构】 - 图3
本来 vdom 里通过 children 关联父子节点,而 fiber 里面则是通过 child 关联第一个子节点,然后通过 sibling 串联起下一个,所有的节点可以 return 到父节点。
这样不就把一颗 vdom 树,变成了 fiber 链表么?
然后渲染 fiber 就可以了,和渲染 vdom 的时候一样。
为什么费这么多事转成另一种结构再渲染呢?这不是多此一举么?
那肯定不是,fiber 架构的意义在这:
之前我们是递归渲染 vdom 的,然后 diff 下来做 patch 的渲染:
2022/04/12 【手写简易版 React 来彻底搞懂 fiber 架构】 - 图4
这个渲染和 diff 是递归进行的。
现在变成了这样:
2022/04/12 【手写简易版 React 来彻底搞懂 fiber 架构】 - 图5
先把 vdom 转 fiber,也就是 reconcile 的过程,因为 fiber 是链表,就可以打断,用 schedule 来空闲时调度(requestIdleCallback)就行,最后全部转完之后,再一次性 render,这个过程叫做 commit。
这样,之前只有 vdom 的 render 和 patch,现在却变成了 vdom 转 fiber 的 reconcile,空闲调度 reconcile 的 scdule,最后把 fiber 渲染的 commit 三个阶段。
意义就在于这个可打断上。因为递归渲染 vdom 可能耗时很多,JS 计算量大了会阻塞渲染,而 fiber 是可打断的,就不会阻塞渲染,而且还会在这个过程中把需要用到的 dom 创建好,做好 diff 来确定是增是删还是改。
dom 有了,增删改也知道了咋做了,一次性 commit 不就很快了么。
这就是 fiber 架构的意义!
接下来我们实现下。