Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react的理念和架构 #31

Open
dark9wesley opened this issue May 26, 2021 · 0 comments
Open

react的理念和架构 #31

dark9wesley opened this issue May 26, 2021 · 0 comments
Labels

Comments

@dark9wesley
Copy link
Owner

react的理念是构建快速响应的web应用程序,为了达成快速响应,首先就要避免页面的卡顿和掉帧。

而导致页面卡顿和掉帧的原因主要有一个:

  • JS大计算量的操作或设备性能不足导致的卡顿

时间切片-可中断的异步更新

现在主流浏览器刷新频率是60Hz,也就是每(1000ms/60Hz)16.6ms刷新一次。

由于JS线程和GUI渲染线程是互斥的,所以JS执行与页面布局、页面渲染不能同时执行。

当JS遇到大计算量的操作,执行时间超过了16.6ms时,这次刷新就没有剩余时间留给页面布局和渲染了,就会引起卡顿和掉帧。

React是如何解决这个问题的呢?其实道理也很简单,就是将需要大量时间执行的长任务切分成一段一段的短任务,化繁为简。

在浏览器每一帧的时间里,React预留一部分时间给自己更新组件,当预留时间不够用时,就把线程控制权交还给GUI线程使其有时间来渲染UI,React则等到下一帧时间来继续被中断的任务。

在React源码中,预留的初始时间是5ms
这种将长任务分拆到每一帧中的操作,就叫做时间切片(time slice)

总结来说,解决JS大计算量操作或设备性能不足导致的卡顿,关键就是将同步的更新变为可中断的异步更新

React15架构

React15架构可以分为两部分:

  • Reconciler(协调器) —— 负责找出变化的组件
  • Renderer(渲染器) —— 负责将变化的组件渲染到页面上

Reconciler(协调器)

在React中可以通过this.setStatethis.forceUpdateReactDOM.render等API来触发更新。

每当有更新发生时,Reconciler都会做以下工作:

  1. 调用函数组件、或class组件的render方法,将返回的JSX创建为虚拟DOM。(递归更新)
  2. 对比新旧虚拟DOM,通过对比找出变化的虚拟DOM
  3. 通知Renderer将变化的虚拟DOM渲染到页面上

Renderer(渲染器)

在每次更新,Render接收到Reconciler的通知时,就会把变化的虚拟DOM渲染到当前宿主环境。

React15架构的缺点

在Reconciler中,mount的组件会调用mountComponent方法,update的组件会调用updateComponent方法。

这两个方法都会将JSX递归创建为虚拟DOM,更新组件及其所有子组件。

由于采用了递归更新,所以当递归的层级过深(嵌套的组件过多),更新时间超过了16.6ms时,用户就会感知到卡顿。

在上面已经提出了解决的方法,就是采用时间切片来分割长任务,将同步更新变为可中断的异步更新。

但可惜的是React15并不支持这样做,因为在React15中Reconciler和Renderer是交替工作的,是同步不可中断的。

如果中断就会导致DOM更新不完全的问题,所以在React16中,整个架构都重写了。

Reconciler每找到一个需要更新的DOM节点就会立刻通知Renderer,Renderer将其更新后,Reconciler再继续,以此反复。

由于整个过程都是同步的,也就是React完成工作后才会交给GUI渲染线程来渲染页面,所以在用户看来所有DOM是同步更新的。

React16架构

React16架构可以分为三部分:

  • Scheduler(调度器) —— 负责调度任务的优先级,以及时间切片
  • Reconciler(协调器) —— 负责找出变化的组件
  • Renderer(渲染器) —— 负责将变化的组件渲染到页面上

Scheduler(调度器)

Scheduler负责在浏览器有剩余时间进行通知,让Reconciler继续工作。并且还能调度任务的优先级,高优先级的任务先进入Reconciler。

Reconciler(协调器)

在React15中,Reconciler是以递归的方式来处理虚拟DOM的,这种方式存在不可中断的弊端。

在React16中,Reconciler内部采用了Fiber的架构,递归变成了可中断的循环过程,每次循环都会调用shouldYield来判断是否有剩余时间。

/** @noinline */
function workLoopConcurrent() {
  // Perform work until Scheduler asks us to yield
  while (workInProgress !== null && !shouldYield()) {
    workInProgress = performUnitOfWork(workInProgress);
  }
}

并且在React16中,Reconciler和Renderer不再是交替工作。而是在Scheduler将任务交给Reconciler后,Reconciler会给所有变化的虚拟DOM打上标记,最后统一交给Renderer处理。

需要注意的是,整个Scheduler与Reconciler的工作都在内存中进行,只有两个原因会中断工作:

  1. 有更高优先级任务需要更新
  2. 当前帧没有剩余时间

Renderer(渲染器)

Renderer根据Reconciler为虚拟DOM打的标记,同步执行对应的DOM操作。

由于整个Scheduler与Reconciler的工作都在内存中进行,即使任务反复中断,用户也不会看到更新不完全的DOM。

参考

React技术揭秘

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant