React架构:
react 16版本的架构可以分为三层: 调度层,协调层,渲染层。
- Scheduler 调度层: 调度任务的优先级,高优先任务优先进入协调器。
- Reconciler 协调层: 构建fiber数据结构,比对Fiber对象找出差异,记录Fiber对象要进行的DOM操作。
- Renderer 渲染层: 负责将发生变化的部分渲染到页面上。
1、 Scheduler 调度层
react 15版本中是没有调度层的,采用的是递归的方式进行virtualDOM的比对,由于递归使用JavaScript自身的执行栈,一旦开始就无法停止,直到任务执行完成。如果VirtualDOM树的层级比较深,virtualDOM的比对就会长期占用JavaScript的主线程。由于JavaScript的单线程机制,无法同时执行其他任务,所以在virtualDOM比对的过程中无法响应用户操作,无法即时执行元素动画,造成了页面卡顿的现象。
所以在React 16版本中,就加入了调度层,采用循环模拟递归的方式,而且比对的过程时利用浏览器的空闲时间完成的,不会长时间占用主线程,所以这就解决了virtualDOM 比对造成的页面卡顿现象问题。
虽然 window对象中提供的requestIdleCallback API,它可以利用浏览器的空闲时间执行任务,但是它自身存在一定的问题,比如比非所有的浏览器都兼容,而且它的触发频率也不是很稳定,所以React最终放弃了requestIdleCallback 的使用。官方实现了自己的任务调度库,这个库就叫做Scheduler。它也可以实现在浏览器空闲时间时执行任务,而且还可以设置任务的优先级,高优先级任务先执行,低优先级任务后执行。
2、Reconciler 协调层
在react 15版本中,协调器和渲染器交替执行,即找到差异就直接更新差异。在React 16版本中,这种情况发生了变化,协调器和渲染器不再交替执行。协调器负责找出差异,在所有差异找出之后,统一交给渲染器进行DOM的更新。
也就是说协调器的主要任务就是是找出差异部分,并为差异打上标记。
3、Renderer 渲染层
渲染层是根据协调层为fiber节点打的标记,同步执行对应的DOM的操作。
既然比对的过程从递归变成了可以中断的循环,那么React是如何解决中断更新时DOM渲染不完全的问题的呢?
其实根本不存在这个问题。因为在整个过程中,协调器和调度器的工作是在内存中完成的是完全可以被打断的,渲染器的工作则是被设定成了不可被打断的。所以也就不存在DOM渲染不完全的问题。
数据结构
4、Fiber 数据结构
fiber其实就是JavaScript对象,他是从virtualDOM对象演变而来的。
type Fiber = {
/************************ DOM 实例相关 *****************************/
// 标记不同的组件类型, 值详见 WorkTag
tag: WorkTag,
// 组件类型 div、span、组件构造函数
type: any,
// 实例对象, 如类组件的实例、原生 dom 实例, 而 function 组件没有实例, 因此该属性是空
stateNode: any,
/************************ 构建 Fiber 树相关 ***************************/
// 指向自己的父级 Fiber 对象
return: Fiber | null,
// 指向自己的第一个子级 Fiber 对象
child: Fiber | null,
// 指向自己的下一个兄弟 iber 对象
sibling: Fiber | null,
// 在 Fiber 树更新的过程中,每个 Fiber 都会有一个跟其对应的 Fiber
// 我们称他为 current <==> workInProgress
// 在渲染完成之后他们会交换位置
// alternate 指向当前 Fiber 在 workInProgress 树中的对应 Fiber
alternate: Fiber | null,
/************************ 状态数据相关 ********************************/
// 即将更新的 props
pendingProps: any,
// 旧的 props
memoizedProps: any,
// 旧的 state
memoizedState: any,
/************************ 副作用相关 ******************************/
// 该 Fiber 对应的组件产生的状态更新会存放在这个队列里面
updateQueue: UpdateQueue<any> | null,
// 用来记录当前 Fiber 要执行的 DOM 操作
effectTag: SideEffectTag,
// 存储第一个要执行副作用的子级 Fiber 对象
firstEffect: Fiber | null,
// 存储下一个要执行副作用的子级 Fiber 对象
// 执行 DOM 渲染时要先通过 first 找到第一个, 然后通过 next 一直向后查找
nextEffect: Fiber | null,
// 存储 DOM 操作完后的副作用 比如调用生命周期函数或者钩子函数的调用
lastEffect: Fiber | null,
// 任务的过期时间
expirationTime: ExpirationTime,
// 当前组件及子组件处于何种渲染模式 详见 TypeOfMode
mode: TypeOfMode,
};
WorkTag 其实就是0-22之间的数据,用于标识组件的类型。
type WorkTag =
| 0
| 1
| 2
| 3
| 4
| 5
| 6
| 7
| 8
| 9
| 10
| 11
| 12
| 13
| 14
| 15
| 16
| 17
| 18
| 19
| 20
| 21
| 22;
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2;
export const HostRoot = 3;
export const HostPortal = 4;
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const FundamentalComponent = 20;
export const ScopeComponent = 21;
export const Block = 22;
5、React当中的双缓存技术
在React当中,采用了双缓存技术,致力于实现更高效快速的DOM更新。
What is?
例子:当使用Canvas绘制动画时,在绘制每一帧时都会清除上一帧的画面,清除上一帧是需要花费时间的,如果当前帧画面计算量又比较大,又需要花费比较长的时间,这就导致上一帧清除到下一帧显示中间会有比较长的间隙,就会出现短暂的白屏。
为了解决白屏过长的问题,我们可以在内存中绘制当前帧动画,绘制完毕后直接用当前帧替换上一帧画面。这样的话会在帧画面替换的过程中就会节约非常多的时间就不会出现白屏问题了。
这种在内存中构建并直接替换的技术叫做双缓存技术。
React 中使用双缓存技术完成FIber树的构建与替换,实现DOM对象的快速更新。
实现思路:
在react当中,最多会同时存在两颗Fiber树,当前在屏幕中显示的内容对应的Fiber树叫做current Fiber树。当发生更新时,react会在内存中重新构建一颗新的Fiber树,这颗正在构建的Fiber树叫做workInProgress Fiber树。
在双缓存技术中,workInProgress Fiber树就是即将要显示在页面当中的Fiber树,当这颗FIber树构建完成后,React会使用它直接替换current Fiber树以达到快速更新DOM的目的。因为workInProgress Fiber树是在内存中构建的,所以它的构建速度是非常快的。
一旦workInProgress Fiber树在屏幕中呈现,它就会变成current Fiber树。
workInProgress Fiber与current Fiber树的联系:
在current Fiber节点对象中有一个alternate 属性指向对应的workInProgress Fiber节点对象,在workInProgress Fiber节点对象中也有一个alternate 属性指向对应的current Fiber节点对象。
6、fiberRoot 与 rootFiber的区别
fiberRoot 表示Fiber数据结构对象, 是Fiber数据结构中的最外层对象。
rootFiber表示组件挂载点对应的Fiber对象,比如React应用中默认的组件挂载点就是id为root的div。
fiberRoot包含rootFiber,在fiberRoot对象中有一个current属性,存储rootFiber。
rootFiber指向fiberRoot,在rootFiber对象中有一个stateNode属性,指向fiberRoot。
在React应用中 fiberRoot只有一个,而rootFiber可以有多个,因为 render 方法是可以调用多次的。
FiberRoot会记录应用的更新信息,比如协调器在完成工作后,会将工作成果存储在fiberRoot中。