wuyanqian0503 / blogs

1 stars 0 forks source link

【笔记】React技术揭秘 #31

Open wuyanqian0503 opened 3 years ago

wuyanqian0503 commented 3 years ago

React理念

快速响应——将同步更新转变为可中断的异步更新

React从V15升级到V16,重构了整个架构,付出了巨大的工作量,原因是V15的老架构无法满足快速响应的需求。

老的React架构分为两层:

渲染器

渲染器用于管理一棵 React 树,使其根据底层平台进行不同的调用,并将组件渲染到页面上。

因为需要支持跨平台,所以有了渲染器,不同的平台采用不同的渲染器。

reconcilers

虽然由于适用的平台不同,React DOM 和 React Native 渲染器的区别很大,但也需要共享一些逻辑。特别是协调算法需要尽可能相似,这样可以让声明式渲染,自定义组件,state,生命周期方法和 refs 等特性,保持跨平台工作一致

为了解决这个问题,不同的渲染器彼此共享一些代码。我们称 React 的这一部分为 “reconciler”。当处理类似于 setState() 这样的更新时,reconciler 会调用树中组件上的 render(),然后决定是否进行挂载,更新或是卸载操作。

Stack reconciler

Fiber reconciler

V16的重构主要内容就是采用了Fiber架构。

“fiber” reconciler 是一个新尝试,致力于解决 stack reconciler 中固有的问题,同时解决一些历史遗留问题。Fiber 从 React 16 开始变成了默认的 reconciler。

它的主要目标是:

虽然这已经在 React 16 中启用了,但是 async 特性还没有默认开启。

老的React架构的缺点

递归比对节点的变更,同步执行,并且难以中断,在遇到复杂的计算时,容易阻塞页面的渲染,或者无法对用户的操作及时做出响应。

新的React架构

React16架构可以分为三层:

Scheduler 调度器

调度器在浏览器空闲时间调度任务执行,并且提供调度优先级供任务设置。

浏览器其实也提供了类似的API ——requestIdleCallback,但是由于requestIdleCallback存在浏览器兼容问题,以及触发频率不稳定等问题,React借助MessageChanel自己实现了一个调度器,也就是Scheduler。

Reconciler 协调器

ReactV15中的协调器是通过递归来处理虚拟DOM查找变更的组件的,而V16的协调器采用链表循环的方式来比对更新,这种方式可以支持中断,后续根据暂存的上下文对象再恢复执行,比对时对发生变更的对节点打上具体的变更的标记,在协调完成后,形成一颗新的树,交给渲染器将变更的节点渲染到页面上,渲染时会尽量减少工作量,比没有变更的节点会进行克隆复用。

Renderer 渲染器

渲染器Renderer根据Reconciler为虚拟DOM打的标记,同步执行对应的DOM操作,不能中断。

React Fiber可以理解为:

React内部实现的一套状态更新机制。支持任务不同优先级,可中断与恢复,并且恢复后可以复用之前的中间状态。

其中每个任务更新单元为React Element对应的Fiber节点。

人话: React Fiber是react团队在V16中采用的一种架构,他通过将Reconciler设计成通过链表循环来比对的形式,实现了支持协调任务中断,从而能够将一个大的更新的同步任务拆分成一个个小任务,等待浏览器空闲时执行,并且可以通过设置任务的优先级来优化用户体验的一个架构,目的就是为了优化之前版本中同步更新导致的不能快速响的的瓶颈。

Fiber的含义

Fiber包含三层含义:

作为架构来说,之前React15的Reconciler采用递归的方式执行,数据保存在递归调用栈中,所以被称为stack Reconciler。React16的Reconciler基于Fiber节点实现,被称为Fiber Reconciler。

从React16的代码实现上来说,每个Fiber节点对应一个React element,保存了该组件的类型(函数组件/类组件/原生组件...)、对应的DOM节点,而且是在reconciler进行比对时构建的,在比对时还会被附上关联的fiber节点的信息,以此来彼此形成一个链表,从而能够在中断协调任务后,暂存执行上下文,等待后续恢复执行。节点在比对时还会保存本次更新中该组件的改变的状态,要执行的工作。

wuyanqian0503 commented 3 years ago

Diff的瓶颈以及React如何应对

为了降低算法复杂度,React的diff会预设三个限制:

  1. 只对同级元素进行Diff。如果一个DOM节点在前后两次更新中跨越了层级,那么React不会尝试复用他。
  2. 两个不同类型的元素会产生出不同的树。如果元素由div变为p,React会销毁div及其子孙节点,并新建p及其子孙节点。
  3. 开发者可以通过 key prop来暗示哪些子元素在不同的渲染下能保持稳定。