# 源码

# React16架构

React16架构可以分为三层:

  • Scheduler(调度器)—— 调度任务的优先级,高优任务优先进入 Reconciler
  • Reconciler(协调器)—— 负责找出变化的组件
  • Renderer(渲染器)—— 负责将变化的组件渲染到页面上

# Scheduler、Reconciler、Renderer

Reconciler工作的阶段被称为render阶段。因为在该阶段会调用组件的render方法。

Renderer工作的阶段被称为commit阶段。就像你完成一个需求的编码后执行git commit提交代码。commit阶段会把render阶段提交的信息渲染在页面上。

rendercommit阶段统称为work,即React在工作中。相对应的,如果任务正在Scheduler内调度,就不属于work

# 代数效应

代数效应是函数式编程中的一个概念,用于将副作用从函数调用中分离。 假设我们有一个函数getTotalPicNum,传入2个用户名称后,分别查找该用户在平台保存的图片数量,最后将图片数量相加后返回。

function getTotalPicNum(user1, user2) {
  const num1 = getPicNum(user1);
  const num2 = getPicNum(user2);

  return picNum1 + picNum2;
}

我们虚构一个类似try...catch的语法 —— try...handle与两个操作符performresume

function getPicNum(name) {
  const picNum = perform name;
  return picNum;
}

try {
  getTotalPicNum('kaSong', 'xiaoMing');
} handle (who) {
  switch (who) {
    case 'kaSong':
      resume with 230;
    case 'xiaoMing':
      resume with 122;
    default:
      resume with 0;
  }
}

try...catch最大的不同在于:当Errorcatch捕获后,之前的调用栈就销毁了。而handle执行resume后会回到之前perform的调用栈。

# 异步可中断更新

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

为了解决这个问题,React16将递归的无法中断的更新重构为异步的可中断更新。

异步可中断更新可以理解为:更新在执行过程中可能会被打断(浏览器时间分片用尽或有更高优任务插队),当可以继续执行时恢复之前执行的中间状态。

# Fiber

我们可以将纤程(Fiber)、协程(Generator)理解为代数效应思想在JS中的体现。

React Fiber可以理解为:

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

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

Fiber节点可以保存对应的DOM节点。相应的,Fiber节点构成的Fiber树就对应DOM树。

# 双缓存

当我们用canvas绘制动画,每一帧绘制前都会调用ctx.clearRect清除上一帧的画面。

如果当前帧画面计算量比较大,导致清除上一帧画面到绘制当前帧画面之间有较长间隙,就会出现白屏。

为了解决这个问题,我们可以在内存中绘制当前帧动画,绘制完毕后直接用当前帧替换上一帧画面,由于省去了两帧替换间的计算时间,不会出现从白屏到出现画面的闪烁情况。

这种在内存中构建并直接替换的技术叫做双缓存 (opens new window)。

React使用“双缓存”来完成Fiber树的构建与替换——对应着DOM树的创建与更新。

# 双缓存Fiber树

在React中最多会同时存在两棵Fiber树。当前屏幕上显示内容对应的Fiber树称为current Fiber树,正在内存中构建的Fiber树称为workInProgress Fiber树。

current Fiber树中的Fiber节点被称为current fiberworkInProgress Fiber树中的Fiber节点被称为workInProgress fiber,他们通过alternate属性连接。

currentFiber.alternate === workInProgressFiber;
workInProgressFiber.alternate === currentFiber;

React应用的根节点通过current指针在不同Fiber树的rootFiber间切换来实现Fiber树的切换。

workInProgress Fiber树构建完成交给Renderer渲染在页面上后,应用根节点的current指针指向workInProgress Fiber树,此时workInProgress Fiber树就变为current Fiber树。

每次状态更新都会产生新的workInProgress Fiber树,通过currentworkInProgress的替换,完成DOM更新。