React源码自顶向下-理论基础篇

React 设计理念

  • 设计理念:快速响应
  • 制约瓶颈:计算能力和网络延迟也就是 CPU 和 IO
  • 解决方法:异步可中断更新和人机交互

主流浏览器的渲染是 60hz,也就是 1000ms/60hz = 16.6ms 浏览器刷新一次。在此过程中,浏览器要进行 JS 的执行 -> 样式布局 -> 样式绘制,如果在这一帧中这三个过程不能全部完成,就要出现掉帧的情况。

如何优化 CPU?

异步可中断更新:浏览器将一帧的一部分时间预留给 react,react 利用这部分时间来完成自己的工作,如果每个长任务超过了预留时间,react 会中断自己的工作,并将控制权交还给浏览器,等待下一帧到来 react 才继续之前被中断的工作。这样,浏览器在每一帧都有时间进行样式布局和样式绘制,这样就有效减少了掉帧的可能性。

如何优化 IO?

将人机 交互研究的结果整合到真实 UI 中 Concurrent 模式介绍

React 架构演进史

React15

  1. Reconciler 协调器(采用递归的方式执行,也叫 Stack Reconciler):通过 reconcile(diff 算法)决定哪些组件需要更新
  2. Renderer 渲染器:将需要更新的组件渲染到真实 DOM 上,这里有以下渲染器:

    • ReactDOM:渲染到浏览器,或者 SSR
    • ReactNative:渲染 App 原生组件
    • ReactTest:将组件渲染 成纯 JS 对象,一般用于测试
    • ReactArt:将组件渲染到 Canvas 或者 SVG 上

React15 更新流程 是协调器和渲染器依次执行工作的,本来先更新的组件要比后更新的组件先变化,但由于整个过程都是同步的,所以看不出变化。

但是如果把同步更新变为异步可中断的更新,那中断之后,对于需要批量更新的组件,后更新的组件不会渲染到视图上,而先更新的组件已经渲染到了视图上,感觉就像一个 bug。

React16

  1. 新增 Scheduler 调度器:决定高优先的任务先进入协调器。
  2. Reconciler 协调器(基于 Fiber 节点实现,也叫 Fiber Reconciler):通过 reconcile(diff 算法)创建虚拟 DOM 树,给需要更新的组件打上Update标记,决定哪些组件需要批量更新
  3. Renderer 渲染器:接收到通知,将被打了Update标记的组件,也就是将需要更新的组件渲染到真实 DOM 上

React16 的流程是 如果协调器中有任务正在 diff,但是调度器有更高优的任务进来,那刚才的任务就会中断执行,反而先执行高优的任务。

由于调度器和协调器都是在内存中工作的,所以即使有中断发生,用户也不会看到更新不完全的视图。

React 新架构-Fiber

代数效应

  • 代数效应是函数式编程中的概念,用于将副作用从函数调用中分离。
  • react 通过代数效应来实现异步可中断的更新。

什么是 Fiber

  • Fiber 纤程
  • Process 进程
  • Thread 线程
  • Coroutine 协程

JS 已通过 Generator 实现了协程,为什么 React 还要自己实现一个 Fiber 纤程呢?

  1. Generator 具有传染性,如果一个函数变成了 generator,那么调用它的函数也会变成 generator,从而受到了影响。
  2. 最重要的一点是:Fiber 架构为了达到两个目的:

    1. 异步可中断并且继续
    2. 更新具有优先级,高优先级的更新可以打断低优先级的更新。generator 可以实现第一点,但是无法实现第二点。

官方 issue 解释

Fiber 架构的原理

  1. 作为架构来说,之前 React15 的 Reconciler 采用递归的方式执行,数据保存在递归调用栈中,所以被称为 stack Reconciler。React16 的 Reconciler 基于 Fiber 节点实现,被称为 Fiber Reconciler。
function App() {
  const [value, setValue] = useState(0)
  return (
    <div
      onClick={() => {
        setValue(value + 1)
      }}
    >
      pipihua-{value}
    </div>
  )
}

React.render(<App />, document.getElementById("root"))

fiber节点的联系

  1. 作为静态数据结构来说,每个 Fiber 节点对应了一个组件 React Element,保存了组件的相关信息,比如该组件的类型、对应的 Dom 节点等信息,这时的 Fiber 节点也就是我们所说的虚拟 Dom。
  2. 作为动态工作单元来说,每个 Fiber 节点保存了本次更新相关的信息

Fiber 的工作原理

  • 采用双缓存的工作机制 Fiber 通过当前屏幕上显示内容对应的 Fiber 树称为 current Fiber 树和正在内存中构建的 Fiber 树称为 workInProgress Fiber 树实现双缓存。通过直接在内存中绘制workInProgress Fiber 树,绘制完毕后直接替换current Fiber 树来实现页面的 dom 的更新。

总结

  1. 如何做到快速响应

    异步可中断更新和 concurrent 模式

  2. 为什么异步可中断更新可以实现快速响应

    当渲染大量 dom 节点这种 CPU 密集型操作时,同步更新会导致掉帧效果,而异步可中断更新不会

  3. 为什么同步更新在 CPU 密集时会导致掉帧

    当同步的 js 计算时间过长产生长任务时,浏览器就没时间去做样式绘制和布局了

  4. 怎样实现异步可中断更新

    Reconciler 协调器 实现了 Fiber ,Fiber 可实现异步可中断的更新

  5. 为什么要有 React Fiber

    Generator 协程可实现中断后恢复,但是不能实现任务优先级调度。所以 React 内部实现的一套状态更新机制。Fiber 支持任务不同优先级,可中断与恢复,并且恢复后可以复用之前的中间状态。

  6. Fiber 是如何实现异步可中断更新

    不晓得,这可能就是代数效应吧,我们不知道实现,但是知道它可。待源码查询一下 // TODO:

  7. 为什么 react 15 不能实现异步可中断更新

    在 React15 及以前,Reconciler 采用递归的方式创建虚拟 DOM,递归过程是不能中断的。如果组件树的层级很深,递归会占用线程很多时间,造成卡顿。

参考

Published under  on .

Last updated on .

pipihua

我是皮皮花,一个前后端通吃的前端攻城狮,如果感觉不错欢迎点击小心心♥(ˆ◡ˆԅ) star on GitHub!