Fiber 架构 即 React v16 之后新的调度算法(reconciliation algorithm), 可以提升复杂 React 应用的响应速度和性能

React 即 reconsider(调度者), react-dom 则是 renderer, 调度者一直都是由 React 本身决定, 而 render 则可以由社区控制和贡献

原由

同步更新过程的局限; 在现有 React 中, 更新过程是同步的, 这意味着当 React 加载/更新组件树时, 调用各组件的生命周期、计算/对比 Virtual DOM、更新 DOM 树, 整个过程中间不会停下; 而又因为 JavaScript 在浏览器的主线程上运行, 所以当组件树比较大的时候, 如果 JavaScript 运行时间过长, 就会阻塞这些其他工作, 可能会导致性能问题(界面卡顿、用户体验不好)

React 运行时存在三种实例

  1. DOM - 真实 DOM 节点
  2. Instances - React 维护的 Virtual DOM Tree node, 它是根据 Elements 创建的, 对组件及 DOM 节点的抽象表示; Virtual DOM Tree 未回了组件状态及组件与 DOM 树的关系
  3. Elements - 描述 UI 长什么样子 (type, props)

更新后的在 Instances 层新增了实例

  1. DOM - 真实 DOM 节点
  2. Instances
    1. effect
      • 每个 workInProgress Tree 节点上都有一个 effect list
      • 用来存放 diff 结果
      • 当前节点更新完毕会向上 merge effect list (queue 收集 diff 结果)
    2. workInProgress
      • workInProgress tree 是 reconcile 过程中从 fiber tree 建立的当前进度快照, 用于断点恢复
    3. fiber
      • fiber tree 与 Virtual DOM Tree 类似, 用来描述增量更新所需的上下文信息
  3. Elements - 描述 UI 长什么样子 (type, props)
    1
    2
    3
    4
    5
    6
    7
    8
    // fiber tree 实际上是一个单链表(Singly Linked List) 树结构
    {
    stateNode, // 状态节点
    child, // 子节点
    sibling, // 兄弟节点
    return, // 表示当前节点处理完毕后, 应该像谁提交自己的成果(effect list)
    ...
    }

方式

分片; React Fiber 把更新过程碎片化(即把渲染/更新过程拆分成一系列小任务), 每执行完一段更新过程, 就把控制权交还给 React 负责任务协调的模块, 看看有没有其他紧急任务要做, 如果没有就继续更新, 如果有紧急任务, 优先执行紧急任务; 维护每一个分片的数据结构, 就是 React Fiber

原理/机制

React Fiber 会将一次更新过程分为多个分片来完成, 所以完全有可能一个更新任务没有完成, 就被另一个优先级更高的更新过程打断, 这时候, 优先级高的更新任务会优先处理完, 而优先级低的更新任务所做的工作则会完全作废, 等待机会重头再来

Fiber 数据结构 + 新的算法 = 大量计算可以被拆解, 异步化, 浏览器主线程得以释放, 保证渲染帧率, 从而提高响应性

React Fiber 更新的两个阶段

  1. (diff 阶段) render / reconciliation: 可打断, React 在 workingProgressTree 上复用 current 上的 Fiber 数据结构(fiber tree)来进一步(通过 requestIdleCallback 调度方法) 来构建新的 tree, (对比 prevInstance 和 nextInstance 的状态) 标记出需要更新的节点, 放入队列中(找出需要更新哪些 DOM)
    • 过程
      1. 如果当前节点不需要更新, 直接把子节点 clone 过来, 跳到步骤5, 需要更新则打上tag
      1. 更新当前节点状态(props, state, context等)
      1. 调用shouldComponentUpdate(), false的话, 跳到步骤5
      1. 调用 render() 获得新的子节点, 并为子节点创建 fiber(创建过程会尽量复用现有 fiber, 子节点增删也发生在这里)
      1. 如果没有产生 child fiber, 该工作单元结束, 把 effect list 归并到 return, 并把当前节点的 sibling 作为下一个工作单元, 否则把 child 作为下一个工作单元
      1. 如果没有剩余可用时间了, 等到下一次主线程空闲时才开始下一个工作单元, 否则立即开始做
      1. 如果没有下一个工作单元了(回到了workInProgress tree的根节点), render/reconciliation 阶段结束,进入 pendingCommit 状态
    • 实际上是 1-6 的工作循环, 7是出口; 工作循环每次只做一件事, 做完看要不要喘口气
  2. (patch 阶段) commit: 不可打断, React 将其所有的变更一次性更新到 DOM 上(同步执行)
    • 处理 effect list (包括3种处理: 更新DOM树、调用组件生命周期函数以及更新 ref 等内部状态)
    • 该阶段结束时, 所有更新都 commit 到 DOM 树上了

      注意: Fiber 会导致的问题就是在 render 前(第一阶段) 中的生命周期函数在一次加载和更新过程中的可能会被多次调用; 所以, 如果逻辑是假设在一个更新过程中只调用一次的话, 可能会需要修改

生命周期

生命周期被分为两个阶段

1
2
3
4
5
6
7
8
9
// render/reconciliation 阶段
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWilllUpdate
// commit 阶段
componentDidMount
componentDidUpdate
componentWillUnmount

render / reconciliation 阶段的生命周期函数可能会被多次调用, 默认以低优先级执行, 被高优先级任务打断的话, 稍后重新执行

React-Fiber