写点什么

之 React Fiber

作者:flyzz177
  • 2022 年 9 月 27 日
    浙江
  • 本文字数:13522 字

    阅读完需:约 44 分钟

开始之前,先讲一下该文章能帮你解决哪些问题?开始之前,先讲一下该文章能帮你解决哪些问题?


  • facebook 为什么要使用重构 React

  • React Fiber 是什么

  • React Fiber 的核心算法 - react 是如何中断重启任务的

  • react fiber 部分源码简化版

前言

该文章涉及的源码部分基于 React v17.0.2

why React Fiber

  • 浏览器渲染过程


从浏览器的运行机制谈起。大家都知道,浏览器是多进程多线程的,多进程包括主进程,渲染进程,插件进程,GPU 进程等,作为前端开发者,我们主要关注其中的渲染进程,这里是页面渲染,HTML 解析,css 解析,js 执行所在的地方。在渲染进程中包括多个线程,此次核心关注页面渲染的两个线程,GUI 线程和 JS 线程。


GUI 线程负责浏览器界面的渲染,包括解析 HTML,CSS,布局绘制等;js 线程包含我们通常编写的 js 代码的解析引擎,最有名的就是 google 的 V8。需要注意的一点是,js 引擎和 GUI 渲染是互斥的,因为 JS 可能会更改 HTML 或者 CSS 样式,如果同时执行会导致页面渲染混乱,所以当 JS 引擎执行时,GUI 渲染线程会被挂起,等 JS 引擎执行完立即执行。


  • GPU 渲染


我们通常看到的动画,视频本质上是通过一张张图片快速的闪过,欺骗人类的双眼,让人以为是连续的动画,每秒内包含的图片越多动画越流畅,正常 60 张图片可以让人眼感觉是流畅的动画,所以当前大部分设备的 FPS 是 60,即,每秒 60 帧。所以 Chrome 要在 16ms 的时间内执行完下图的渲染任务,才能让用户感觉不到掉帧。



所以,如果 JS 执行时间过长,基本上超过 10ms 之后,用户会感觉到明显的卡顿,很影响用户体验(下文中 js 执行都以 16ms 为分界点,不计算后续的渲染,实际的可执行时间肯定小于 16ms)。而 React 执行是要进行两棵树的 diff,虽然 React 根据 html 的特性对 diff 算法做了优化,但是如果两棵树比对的层级较深,依旧会远远超过 16ms。

相关参考视频讲解:进入学习

React Fiber

基于此,那如何解决问题呢?在上图中,React 作为 js,所有的同步操作执行在最开始,在 React 执行完成后,后续的 html 解析,布局渲染等操作才会执行。最容易想到的就是,优化 JS 的执行速度,把 React 占用线程的时间缩短到 16ms 以内。在 React 执行中,最耗时的就是 diff 算法,React 针对 html 这种场景下做了优化,业界已经没有更好的算法可以缩短 diff 算法的时间,所以当树的层次很深时,执行时间依旧很长。


那还有什么办法呢,我们依旧可以看上图,在现代浏览器中,浏览器为了让开发者知道浏览器执行完当前帧所有的操作后,还有多长时间可以使用,提供了 requestIdleCallback 这么一个方法,据此我们可以知道当前还有多长时间可以执行。

requestIdleCallback
requestIdleCallback((deadline) => {    while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && nextComponent) {        nextComponent = performWork(nextComponent);    }});
复制代码


题外话:

有兴趣可以在控制台执行输出一下 requestIdleCallback 回调参数的 requestIdleCallback((deadline) ,在不同的网页上得到的时间可能不同。甚至可能会超过 16ms(在 React 官网就显示 49.9ms)因为 requestIdleCallback 的一些限制原因,React 源码中未使用 requestIdleCallback,而是自己实现了一套类似的机制。


使用此方法我们知道每帧的剩余时间之后,这样就可以在剩余时间内进行工作,如果当前帧时间不够,就把剩余的工作放到下一帧的 requestIdleCallback 中执行。这就是 React 所说的时间切片(time slicing)。


所以要使用此方法,需要把基于 js 内置栈调用的同步递归遍历的 diff 算法改为异步增量更新。按照 React 负责人的说法就是


如果你仅仅依赖 js 内置的调用栈,它会一直执行直到栈为空...,如果我们可以任意的中断并且手动的操作调用栈,不是更完美吗?这就是 React Fiber 的目的。Fiber 是针对 React Component 的栈的重新实现。你可以认为一个 Fiber 就是一个虚拟的栈中的一项任务。


说人话,就是原来树的递归是深度递归遍历,现在需要把递归算法重新实现,以便于我不依赖于栈的调用,可以对 react 组件一个一个节点的遍历,中途任意时间可以中断和从当前开始。

stack Reconciliation vs Fiber Reconciliation

stack Reconciliation

假如我们有如下一个 html 结构



转化成类 React 组件的 js 对象如下


const a1 = {name: 'a1'};const b1 = {name: 'b1'};const b2 = {name: 'b2'};const b3 = {name: 'b3'};const c1 = {name: 'c1'};const c2 = {name: 'c2'};const d1 = {name: 'd1'};const d2 = {name: 'd2'};
a1.render = () => [b1, b2, b3];b1.render = () => [];b2.render = () => [c1];b3.render = () => [c2];c1.render = () => [d1, d2];c2.render = () => [];d1.render = () => [];d2.render = () => [];
复制代码


正常情况,我们会使用像下面这种方式递归来遍历这棵"树",在 React 最早的版本就是基于此来递归遍历 dom 树


function walk(instance) {  console.log(instance.name);  let children = instance.render();  children.forEach((child) => {    walk(child);  });}walk(a1);
复制代码


可以看到,这种方式,是可以遍历完整棵树,可是它没办法做到我们之前所说的中断递归,如果你中途中断了递归这棵树,下次你要重新从根节点整个遍历。这显然是不行的,它只能不断递归遍历,直到 stack 调用栈为空。那 React Fiber 是如何中断重启任务呢?


答案是单链表树遍历算法。简单来说就是把原来树本身的嵌套结构,改为单链表形式的树。

Fiber Reconciliation

React 具体是如何使用链表遍历树呢?为了实现这种算法,首先先看下我们需要的数据结构


  • child,指向该节点第一个子节点

  • sibling,指向该节点的下一个兄弟节点

  • return,指向该节点的父节点


还是之前的 dom 树结构,现在变成了这样



构建 Fiber 树的过程就不描述了,我们直接看遍历算法(父节点优先,深度优先)


let root = fiber;let node = fiber;while (true) {  if (node.child) {    node = node.child;    continue;  }  if (node === root) {    return;  }  while (!node.sibling) {    if (!node.return || node.return === root) {      return;    }    node = node.return;  }  node = node.sibling;}
复制代码


可以看到,拿到根节点后,不断遍历子节点,直到最深节点,然后从最深的子节点开始遍历兄弟节点,如果没有兄弟节点就返回该节点父节点,如果有兄弟节点,把每个兄弟节点的子节点遍历完,直到最后一个子节点,然后返回父节点。这样不断遍历,直到返回根节点。


下面是在 React 源码中 Fiber 的数据对象。其实说到底,Fiber 就是一个对象。他相对于之前 React createElement 生成的 element 对象,多了一层数据结构来支撑上述的单链表遍历算法。

Fiber 数据结构

下面是 React 源码中的 Fiber 对象的属性,具体可以直接看注释。


function FiberNode(  tag: WorkTag,  pendingProps: mixed,  key: null | string,  mode: TypeOfMode) {  // Instance  this.tag = tag; //Fiber标记是什么类型的Fiber/component,WorkTag 0-24  this.key = key; // 唯一标识  this.elementType = null;  this.type = null;  this.stateNode = null; //stateNode:class div
// Fiber 数据结构 this.return = null; // 父节点 this.child = null; // 第一个子节点 this.sibling = null; // 兄弟节点 this.index = 0; //
this.ref = null;
this.pendingProps = pendingProps; //newprops this.memoizedProps = null; // oldProps 上次的props // updateQueue数据结构: // { // baseState: fiber.memoizedState, // firstBaseUpdate: null, // lastBaseUpdate: null, // shared: { // pending: null, // interleaved: null, // lanes: NoLanes, // }, // effects: null, // }; this.updateQueue = null; // 批处理队列
this.memoizedState = null; //oldState this.dependencies = null;
this.mode = mode;
// Effects this.flags = NoFlags; // 标记该fiber变更方式 this.subtreeFlags = NoFlags; this.deletions = null; // 优先级调度 this.lanes = NoLanes; this.childLanes = NoLanes;
this.alternate = null; //work-in-progress current互为alternate}
复制代码

fiber 带来的效果提升

  1. 可以通过看下重构前后的对比 Demo,体会一下带来的体验提升

  2. 为后续 React Concurrent 模式做了基础

Fiber 流转过程

画了一个简单的流程图说明 Fiber 的流转流程。


图示说明:

react 在 performUnitOfWork 和 completeUnitOfWork 两个方法中,处理上述 Fiber 遍历算法的逻辑,在 beginwork 和 completeWork 中完成处理组件的逻辑。在 beginwork 中会处理 state 的更新,此阶段相应生命周期的调用,reconcile 的过程(给 Fiber 节点打上新增,删除,移动等标记的过程。在 completeWork 阶段,会把所有 flags 的标记,冒泡到父节点。以便于在 commit 阶段更新。


我记得 Dan Abramov 对 effect list 有过一个形象的比喻,可以写一下(大致意思是这样)


你可以把 react fiber 看做一棵圣诞树,effect list 就是这颗圣诞树上悬挂的装饰灯

React 源码 ---太长不看系列

下面是 React 中关于 Fiber 的一些核心源码---已删除了很多跟此次文章无关的代码,大家可以自行选择是否服用。


包含代码注释,及代码在 React 仓库中的所在位置。大家可以直接看代码注释,不作具体解读了。


// https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1635function workLoopConcurrent() {  // Perform work until Scheduler asks us to yield  while (workInProgress !== null && !shouldYield()) {    performUnitOfWork(workInProgress);  }}
复制代码


performUnitOfWork


// https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1642function performUnitOfWork(unitOfWork: Fiber): void {  const current = unitOfWork.alternate;  let next;  // 一直返回unitOfWork.child,不会处理sibling    next = beginWork(current, unitOfWork, subtreeRenderLanes);  unitOfWork.memoizedProps = unitOfWork.pendingProps;  // 该fiber需要做的处理完成,返回下一个待处理的fiber  if (next === null) {    // 到达该链路的最底层的叶子节点,在该函数中处理sibling节点    completeUnitOfWork(unitOfWork);  } else {    workInProgress = next;  }}
复制代码


beginWork


// https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L3083function beginWork(  current: Fiber | null,  workInProgress: Fiber,  renderLanes: Lanes): Fiber | null {  let updateLanes = workInProgress.lanes;    // tag有很多,这里只保留了常用的FunctionComponent和ClassComponent,后续只看updateClassComponent  switch (workInProgress.tag) {    case FunctionComponent: {      const Component = workInProgress.type;      const unresolvedProps = workInProgress.pendingProps;      const resolvedProps =        workInProgress.elementType === Component          ? unresolvedProps          : resolveDefaultProps(Component, unresolvedProps);      return updateFunctionComponent(        current,        workInProgress,        Component,        resolvedProps,        renderLanes      );    }    case ClassComponent: {      const Component = workInProgress.type;      const unresolvedProps = workInProgress.pendingProps;      const resolvedProps =        workInProgress.elementType === Component          ? unresolvedProps          : resolveDefaultProps(Component, unresolvedProps);      // 返回值为workInProgress.child,可以在finishClassComponent中看到      return updateClassComponent(        current,        workInProgress,        Component,        resolvedProps,        renderLanes      );    }  }}function updateClassComponent(  current: Fiber | null,  workInProgress: Fiber,  Component: any,  nextProps: any,  renderLanes: Lanes) {
const instance = workInProgress.stateNode; let shouldUpdate; // 在此阶段处理更新生命周期和批处理的更新, if (instance === null) { if (current !== null) { // A class component without an instance only mounts if it suspended // inside a non-concurrent tree, in an inconsistent state. We want to // treat it like a new mount, even though an empty version of it already // committed. Disconnect the alternate pointers. current.alternate = null; workInProgress.alternate = null; // Since this is conceptually a new fiber, schedule a Placement effect workInProgress.flags |= Placement; } // In the initial pass we might need to construct the instance. constructClassInstance(workInProgress, Component, nextProps); mountClassInstance(workInProgress, Component, nextProps, renderLanes); shouldUpdate = true; } else if (current === null) { // In a resume, we'll already have an instance we can reuse. 复用之前未完成 shouldUpdate = resumeMountClassInstance( workInProgress, Component, nextProps, renderLanes ); } else { // 在此阶段处理生命周期和批处理的更新 shouldUpdate = updateClassInstance( current, workInProgress, Component, nextProps, renderLanes ); } const nextUnitOfWork = finishClassComponent( current, workInProgress, Component, shouldUpdate, hasContext, renderLanes ); return nextUnitOfWork;}
function finishClassComponent( current: Fiber | null, workInProgress: Fiber, Component: any, shouldUpdate: boolean, hasContext: boolean, renderLanes: Lanes) { const instance = workInProgress.stateNode; // Rerender ReactCurrentOwner.current = workInProgress; let nextChildren; nextChildren = instance.render(); //初始化或者执行dom diff //ReactChildFiber.old.js reconcileChildren(current, workInProgress, nextChildren, renderLanes); //child return workInProgress.child;}
复制代码


completeUnitOfWork


// https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1670function completeUnitOfWork(unitOfWork: Fiber): void {  // Attempt to complete the current unit of work, then move to the next  // sibling. If there are no more siblings, return to the parent fiber.  let completedWork = unitOfWork;  do {    const current = completedWork.alternate;    const returnFiber = completedWork.return;
let next; // 返回值一直为null next = completeWork(current, completedWork, subtreeRenderLanes); const siblingFiber = completedWork.sibling; if (siblingFiber !== null) { // If there is more work to do in this returnFiber, do that next. workInProgress = siblingFiber; return; } // Otherwise, return to the parent completedWork = returnFiber; // Update the next thing we're working on in case something throws. workInProgress = completedWork; } while (completedWork !== null);}
// https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberCompleteWork.old.js#L645function completeWork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes,): Fiber | null { const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) { case FunctionComponent: bubbleProperties(workInProgress); return null; case ClassComponent: { const Component = workInProgress.type; if (isLegacyContextProvider(Component)) { popLegacyContext(workInProgress); } bubbleProperties(workInProgress); return null; } }
复制代码


  • facebook 为什么要使用重构 React

  • React Fiber 是什么

  • React Fiber 的核心算法 - react 是如何中断重启任务的

  • react fiber 部分源码简化版

前言

该文章涉及的源码部分基于 React v17.0.2

why React Fiber

  • 浏览器渲染过程


从浏览器的运行机制谈起。大家都知道,浏览器是多进程多线程的,多进程包括主进程,渲染进程,插件进程,GPU 进程等,作为前端开发者,我们主要关注其中的渲染进程,这里是页面渲染,HTML 解析,css 解析,js 执行所在的地方。在渲染进程中包括多个线程,此次核心关注页面渲染的两个线程,GUI 线程和 JS 线程。


GUI 线程负责浏览器界面的渲染,包括解析 HTML,CSS,布局绘制等;js 线程包含我们通常编写的 js 代码的解析引擎,最有名的就是 google 的 V8。需要注意的一点是,js 引擎和 GUI 渲染是互斥的,因为 JS 可能会更改 HTML 或者 CSS 样式,如果同时执行会导致页面渲染混乱,所以当 JS 引擎执行时,GUI 渲染线程会被挂起,等 JS 引擎执行完立即执行。


  • GPU 渲染


我们通常看到的动画,视频本质上是通过一张张图片快速的闪过,欺骗人类的双眼,让人以为是连续的动画,每秒内包含的图片越多动画越流畅,正常 60 张图片可以让人眼感觉是流畅的动画,所以当前大部分设备的 FPS 是 60,即,每秒 60 帧。所以 Chrome 要在 16ms 的时间内执行完下图的渲染任务,才能让用户感觉不到掉帧。



所以,如果 JS 执行时间过长,基本上超过 10ms 之后,用户会感觉到明显的卡顿,很影响用户体验(下文中 js 执行都以 16ms 为分界点,不计算后续的渲染,实际的可执行时间肯定小于 16ms)。而 React 执行是要进行两棵树的 diff,虽然 React 根据 html 的特性对 diff 算法做了优化,但是如果两棵树比对的层级较深,依旧会远远超过 16ms。

相关参考视频讲解:进入学习

React Fiber

基于此,那如何解决问题呢?在上图中,React 作为 js,所有的同步操作执行在最开始,在 React 执行完成后,后续的 html 解析,布局渲染等操作才会执行。最容易想到的就是,优化 JS 的执行速度,把 React 占用线程的时间缩短到 16ms 以内。在 React 执行中,最耗时的就是 diff 算法,React 针对 html 这种场景下做了优化,业界已经没有更好的算法可以缩短 diff 算法的时间,所以当树的层次很深时,执行时间依旧很长。


那还有什么办法呢,我们依旧可以看上图,在现代浏览器中,浏览器为了让开发者知道浏览器执行完当前帧所有的操作后,还有多长时间可以使用,提供了 requestIdleCallback 这么一个方法,据此我们可以知道当前还有多长时间可以执行。

requestIdleCallback
requestIdleCallback((deadline) => {    while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && nextComponent) {        nextComponent = performWork(nextComponent);    }});
复制代码


题外话:

有兴趣可以在控制台执行输出一下 requestIdleCallback 回调参数的 requestIdleCallback((deadline) ,在不同的网页上得到的时间可能不同。甚至可能会超过 16ms(在 React 官网就显示 49.9ms)因为 requestIdleCallback 的一些限制原因,React 源码中未使用 requestIdleCallback,而是自己实现了一套类似的机制。


使用此方法我们知道每帧的剩余时间之后,这样就可以在剩余时间内进行工作,如果当前帧时间不够,就把剩余的工作放到下一帧的 requestIdleCallback 中执行。这就是 React 所说的时间切片(time slicing)。


所以要使用此方法,需要把基于 js 内置栈调用的同步递归遍历的 diff 算法改为异步增量更新。按照 React 负责人的说法就是


如果你仅仅依赖 js 内置的调用栈,它会一直执行直到栈为空...,如果我们可以任意的中断并且手动的操作调用栈,不是更完美吗?这就是 React Fiber 的目的。Fiber 是针对 React Component 的栈的重新实现。你可以认为一个 Fiber 就是一个虚拟的栈中的一项任务。


说人话,就是原来树的递归是深度递归遍历,现在需要把递归算法重新实现,以便于我不依赖于栈的调用,可以对 react 组件一个一个节点的遍历,中途任意时间可以中断和从当前开始。

stack Reconciliation vs Fiber Reconciliation

stack Reconciliation

假如我们有如下一个 html 结构



转化成类 React 组件的 js 对象如下


const a1 = {name: 'a1'};const b1 = {name: 'b1'};const b2 = {name: 'b2'};const b3 = {name: 'b3'};const c1 = {name: 'c1'};const c2 = {name: 'c2'};const d1 = {name: 'd1'};const d2 = {name: 'd2'};
a1.render = () => [b1, b2, b3];b1.render = () => [];b2.render = () => [c1];b3.render = () => [c2];c1.render = () => [d1, d2];c2.render = () => [];d1.render = () => [];d2.render = () => [];
复制代码


正常情况,我们会使用像下面这种方式递归来遍历这棵"树",在 React 最早的版本就是基于此来递归遍历 dom 树


function walk(instance) {  console.log(instance.name);  let children = instance.render();  children.forEach((child) => {    walk(child);  });}walk(a1);
复制代码


可以看到,这种方式,是可以遍历完整棵树,可是它没办法做到我们之前所说的中断递归,如果你中途中断了递归这棵树,下次你要重新从根节点整个遍历。这显然是不行的,它只能不断递归遍历,直到 stack 调用栈为空。那 React Fiber 是如何中断重启任务呢?


答案是单链表树遍历算法。简单来说就是把原来树本身的嵌套结构,改为单链表形式的树。

Fiber Reconciliation

React 具体是如何使用链表遍历树呢?为了实现这种算法,首先先看下我们需要的数据结构


  • child,指向该节点第一个子节点

  • sibling,指向该节点的下一个兄弟节点

  • return,指向该节点的父节点


还是之前的 dom 树结构,现在变成了这样



构建 Fiber 树的过程就不描述了,我们直接看遍历算法(父节点优先,深度优先)


let root = fiber;let node = fiber;while (true) {  if (node.child) {    node = node.child;    continue;  }  if (node === root) {    return;  }  while (!node.sibling) {    if (!node.return || node.return === root) {      return;    }    node = node.return;  }  node = node.sibling;}
复制代码


可以看到,拿到根节点后,不断遍历子节点,直到最深节点,然后从最深的子节点开始遍历兄弟节点,如果没有兄弟节点就返回该节点父节点,如果有兄弟节点,把每个兄弟节点的子节点遍历完,直到最后一个子节点,然后返回父节点。这样不断遍历,直到返回根节点。


下面是在 React 源码中 Fiber 的数据对象。其实说到底,Fiber 就是一个对象。他相对于之前 React createElement 生成的 element 对象,多了一层数据结构来支撑上述的单链表遍历算法。

Fiber 数据结构

下面是 React 源码中的 Fiber 对象的属性,具体可以直接看注释。


function FiberNode(  tag: WorkTag,  pendingProps: mixed,  key: null | string,  mode: TypeOfMode) {  // Instance  this.tag = tag; //Fiber标记是什么类型的Fiber/component,WorkTag 0-24  this.key = key; // 唯一标识  this.elementType = null;  this.type = null;  this.stateNode = null; //stateNode:class div
// Fiber 数据结构 this.return = null; // 父节点 this.child = null; // 第一个子节点 this.sibling = null; // 兄弟节点 this.index = 0; //
this.ref = null;
this.pendingProps = pendingProps; //newprops this.memoizedProps = null; // oldProps 上次的props // updateQueue数据结构: // { // baseState: fiber.memoizedState, // firstBaseUpdate: null, // lastBaseUpdate: null, // shared: { // pending: null, // interleaved: null, // lanes: NoLanes, // }, // effects: null, // }; this.updateQueue = null; // 批处理队列
this.memoizedState = null; //oldState this.dependencies = null;
this.mode = mode;
// Effects this.flags = NoFlags; // 标记该fiber变更方式 this.subtreeFlags = NoFlags; this.deletions = null; // 优先级调度 this.lanes = NoLanes; this.childLanes = NoLanes;
this.alternate = null; //work-in-progress current互为alternate}
复制代码

fiber 带来的效果提升

  1. 可以通过看下重构前后的对比 Demo,体会一下带来的体验提升

  2. 为后续 React Concurrent 模式做了基础

Fiber 流转过程

画了一个简单的流程图说明 Fiber 的流转流程。


图示说明:

react 在 performUnitOfWork 和 completeUnitOfWork 两个方法中,处理上述 Fiber 遍历算法的逻辑,在 beginwork 和 completeWork 中完成处理组件的逻辑。在 beginwork 中会处理 state 的更新,此阶段相应生命周期的调用,reconcile 的过程(给 Fiber 节点打上新增,删除,移动等标记的过程。在 completeWork 阶段,会把所有 flags 的标记,冒泡到父节点。以便于在 commit 阶段更新。


我记得 Dan Abramov 对 effect list 有过一个形象的比喻,可以写一下(大致意思是这样)


你可以把 react fiber 看做一棵圣诞树,effect list 就是这颗圣诞树上悬挂的装饰灯

React 源码 ---太长不看系列

下面是 React 中关于 Fiber 的一些核心源码---已删除了很多跟此次文章无关的代码,大家可以自行选择是否服用。


包含代码注释,及代码在 React 仓库中的所在位置。大家可以直接看代码注释,不作具体解读了。


// https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1635function workLoopConcurrent() {  // Perform work until Scheduler asks us to yield  while (workInProgress !== null && !shouldYield()) {    performUnitOfWork(workInProgress);  }}
复制代码


performUnitOfWork


// https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1642function performUnitOfWork(unitOfWork: Fiber): void {  const current = unitOfWork.alternate;  let next;  // 一直返回unitOfWork.child,不会处理sibling    next = beginWork(current, unitOfWork, subtreeRenderLanes);  unitOfWork.memoizedProps = unitOfWork.pendingProps;  // 该fiber需要做的处理完成,返回下一个待处理的fiber  if (next === null) {    // 到达该链路的最底层的叶子节点,在该函数中处理sibling节点    completeUnitOfWork(unitOfWork);  } else {    workInProgress = next;  }}
复制代码


beginWork


// https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L3083function beginWork(  current: Fiber | null,  workInProgress: Fiber,  renderLanes: Lanes): Fiber | null {  let updateLanes = workInProgress.lanes;    // tag有很多,这里只保留了常用的FunctionComponent和ClassComponent,后续只看updateClassComponent  switch (workInProgress.tag) {    case FunctionComponent: {      const Component = workInProgress.type;      const unresolvedProps = workInProgress.pendingProps;      const resolvedProps =        workInProgress.elementType === Component          ? unresolvedProps          : resolveDefaultProps(Component, unresolvedProps);      return updateFunctionComponent(        current,        workInProgress,        Component,        resolvedProps,        renderLanes      );    }    case ClassComponent: {      const Component = workInProgress.type;      const unresolvedProps = workInProgress.pendingProps;      const resolvedProps =        workInProgress.elementType === Component          ? unresolvedProps          : resolveDefaultProps(Component, unresolvedProps);      // 返回值为workInProgress.child,可以在finishClassComponent中看到      return updateClassComponent(        current,        workInProgress,        Component,        resolvedProps,        renderLanes      );    }  }}function updateClassComponent(  current: Fiber | null,  workInProgress: Fiber,  Component: any,  nextProps: any,  renderLanes: Lanes) {
const instance = workInProgress.stateNode; let shouldUpdate; // 在此阶段处理更新生命周期和批处理的更新, if (instance === null) { if (current !== null) { // A class component without an instance only mounts if it suspended // inside a non-concurrent tree, in an inconsistent state. We want to // treat it like a new mount, even though an empty version of it already // committed. Disconnect the alternate pointers. current.alternate = null; workInProgress.alternate = null; // Since this is conceptually a new fiber, schedule a Placement effect workInProgress.flags |= Placement; } // In the initial pass we might need to construct the instance. constructClassInstance(workInProgress, Component, nextProps); mountClassInstance(workInProgress, Component, nextProps, renderLanes); shouldUpdate = true; } else if (current === null) { // In a resume, we'll already have an instance we can reuse. 复用之前未完成 shouldUpdate = resumeMountClassInstance( workInProgress, Component, nextProps, renderLanes ); } else { // 在此阶段处理生命周期和批处理的更新 shouldUpdate = updateClassInstance( current, workInProgress, Component, nextProps, renderLanes ); } const nextUnitOfWork = finishClassComponent( current, workInProgress, Component, shouldUpdate, hasContext, renderLanes ); return nextUnitOfWork;}
function finishClassComponent( current: Fiber | null, workInProgress: Fiber, Component: any, shouldUpdate: boolean, hasContext: boolean, renderLanes: Lanes) { const instance = workInProgress.stateNode; // Rerender ReactCurrentOwner.current = workInProgress; let nextChildren; nextChildren = instance.render(); //初始化或者执行dom diff //ReactChildFiber.old.js reconcileChildren(current, workInProgress, nextChildren, renderLanes); //child return workInProgress.child;}
复制代码


completeUnitOfWork


// https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1670function completeUnitOfWork(unitOfWork: Fiber): void {  // Attempt to complete the current unit of work, then move to the next  // sibling. If there are no more siblings, return to the parent fiber.  let completedWork = unitOfWork;  do {    const current = completedWork.alternate;    const returnFiber = completedWork.return;
let next; // 返回值一直为null next = completeWork(current, completedWork, subtreeRenderLanes); const siblingFiber = completedWork.sibling; if (siblingFiber !== null) { // If there is more work to do in this returnFiber, do that next. workInProgress = siblingFiber; return; } // Otherwise, return to the parent completedWork = returnFiber; // Update the next thing we're working on in case something throws. workInProgress = completedWork; } while (completedWork !== null);}
// https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberCompleteWork.old.js#L645function completeWork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes,): Fiber | null { const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) { case FunctionComponent: bubbleProperties(workInProgress); return null; case ClassComponent: { const Component = workInProgress.type; if (isLegacyContextProvider(Component)) { popLegacyContext(workInProgress); } bubbleProperties(workInProgress); return null; } }
复制代码


用户头像

flyzz177

关注

还未添加个人签名 2021.12.07 加入

还未添加个人简介

评论

发布
暂无评论
之React Fiber_React_flyzz177_InfoQ写作社区