react fiber简单了解下

1,475 阅读5分钟

上一篇《知根知底setState》介绍了在不同场景调用setState会有不同的处理过程,但不管怎样最终目的都是要进行react的更新,本文我们就来了解setState之后的更新过程。
提示:源码基于react v16.4.3-alpha.0

需要了解的几个函数

1, findHighestPriorityRoot

字面上意思可以理解为查找拥有最高优先级的root,这个root又是什么呢?

// react 项目的入口文件,初始化
ReactDom.render(
  <App />,
  document.getElementById('app')
)

root就是通过react生成的节点树的根节点 <div id="app"></div>
作用:
1,找到拥有最高优先级的root
2,从更新任务链表中删除没有更新任务的root。这个没有更新任务的root的怎么理解呢——原本这个root的其中某个子节点触发了更新,但是更新已经被处理完了,所以root.expirationTime === NoWork将已处理完成的去除掉

2, addRootToSchedule

在findHighestPriorityRoot函数中可以发现 firstScheduledRoot,lastScheduledRoot表示一个任务调度链表(环形)的头结点和尾节点,那这个链表在哪里生成的呢——addRootToSchedule源码

3,requestWork

总结前两个函数的功能:

  • findHighestPriorityRoot —— 找到更新优先级最高的root,删除已经更新完成的root
  • addRootToSchedule——有新的更新任务,将对应的root插入到任务调度链表

addRootToSchedule的调用位置就是在requestWork源码
看过《知根知底setState》的同学可以知道在将setState的时候到这里就结束了,为什么呢?因为接下来就是react fiber的Reconciler和Commit阶段。

下面进入本文的主题了react fiber,从requestWork开始

一,requestWork

源码

 function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
  addRootToSchedule(root, expirationTime);
  
  ...
  
  if (expirationTime === Sync) {
    // 同步更新
    performSyncWork();
  } else {
    // 异步更新
    scheduleCallbackWithExpirationTime(root, expirationTime);
  }
}   

根据expirationTime判断采用同步更新还是异步更新,本文主要从同步更新的过程来了解fiber

二,performWork

// function performSyncWork() {
  performWork(Sync, null);
} 

performSyncWork仅仅只是调用performWork,注意第二个参数为null

function performWork(minExpirationTime: ExpirationTime, dl: Deadline | null) {
  deadline = dl;
  
  // 保证每一次进行正在处理的更新任务都是优先级最高的
  findHighestPriorityRoot()
  
  if (deadline !== null) {
    // 处理同步更新
    
    // 在当前找到的优先级最高的root上开始后续的更新工作
    performWorkOnRoot(
      nextFlushedRoot,
      nextFlushedExpirationTime,
      currentRendererTime >= nextFlushedExpirationTime,
    );
  } else {
    // 处理异步更
  } 
}

从代码中的if/else可以发现虽然在requestWork中同步和异步走向两个不同的分支,但最后又会合并到同一个处理过程performWork

三,performWorkOnRoot

准备好小板凳和瓜子终于到了fiber的正头戏了,源码

function performWorkOnRoot(
  root: FiberRoot,
  expirationTime: ExpirationTime,
  isExpired: boolean,
) {
  // 同样有两个分支——同步和异步

  if (deadline === null || isExpired) {
    // 同步的处理逻辑
    
    // 我们知道fiber的Reconciler阶段到Commit阶段是可以被中断的
    // finishedWork就是Reconciler阶段diff出来的副作用(新state生成的DOM)
    let finishedWork = root.finishedWork;
    
    if (finishedWork !== null) {
      // 不为空说明之前有任务被中断,需要现在处理完被中断的任务
      completeRoot(root, finishedWork, expirationTime);
      
    } else {
      // 没有被中断的任务,就处理当前找到的优先级最高的更新任务
      
      root.finishedWork = null;
      
      // 是否可以被中断,对于同步和异步到期work不可被中断
      const isYieldy = false;
      
      // Reconciler阶段
      renderRoot(root, isYieldy, isExpired);
      
      // finishedWork其实就是rootWorkInProgress(新的VNode)
      finishedWork = root.finishedWork;
      
      if (finishedWork !== null) {
        // diff之后得到的结果是:有更新,
        // 进入到Commit阶段
        completeRoot(root, finishedWork, expirationTime);
      }
    }
    
  } else {
    // 异步的处理逻辑
  }
}

流程图:

四,Reconciler阶段

源码

function renderRoot(
  root: FiberRoot,
  isYieldy: boolean,
  isExpired: boolean,
): void {
  
  ...

  if (
    expirationTime !== nextRenderExpirationTime ||
    root !== nextRoot ||
    nextUnitOfWork === null
  ) {
    // 检查我们是否从一个新堆栈开始,或者我们是否是从之前的工作中恢复 
    ...
  }
  do {
    try {
      workLoop(isYieldy);
    } catch (thrownValue) {
      ...
    }
    // 执行一次就跳出循环
    break;
  } while (true);
}

workLoop

其实从执行setStateworkLoop如果你能理解,fiber的大概过程你也就理解了。workLoop之后的主要逻辑是进行diff——调用render前生命周期函数,应用新的state执行render得到新的DOM,将要更新的信息挂在到rootWorkInProgress,并最后赋值给finishedWork。
这个过程的代码量比较大,react大多数功能都是在这个阶段进行的(如:ref,context等),当然这部分代码逻辑比较直白式的理解起来也相对简单,如果有兴趣可以自行研究下

五,Commit阶段

源码

function completeRoot(
  root: FiberRoot,
  finishedWork: Fiber,
  expirationTime: ExpirationTime,
): void {

  ...
  
  commitRoot(root, finishedWork);
}
function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
  
  ...
  
  // 执行render之后的生命周期
  // 应用新state生成的Element(在diff阶段通过createElement等创建)到DOM树种
  
}

这个阶段的逻辑起来也比较简单,对这块感兴趣的同学可以自行研究下

结尾

结合《知根知底setState》和本文我们粗略介绍了react v16的更新过程。

你可能会发现本文对一些实现细节没有做过多的描述
这是因为对于fiber实现细节介绍的文章网上已经可以搜索出很多,而且讲的也比较详细。

本文主要在同步更新的过程,异步更新和同步更新大多处理逻辑都是通用的,简单说就以不同的方式进入到更新过程,而且react v16的源码各版本依旧在变动。

我写这篇的目的:

对于想去阅读源码的人为他们提供一个逻辑流程,个中细节他们可以自己慢慢研究(当然这也不是通过一篇两篇文章能描述完整的)

对于只是想了解下内部原理的人省去了理解大量细节处理的烦恼