React源码解析之Fiber

1,937 阅读6分钟

Fiber是什么

Fiber可以看做是Reat中最小的工作单元,它把更新过程碎片化。Fiber与Scheduler配合实现了任务的中断与恢复:当在执行任务过程中,遇到更高优先级的任务,则会中断当前任务,先执行高优先级的任务,高优先级任务执行完成后,再回过头来继续执行被中断的任务。

关于Scheduler可以看之前的文章React源码解析之Scheduler

在更新过程中,Fiber会根据React element深度遍历创建Fiber树,每个React元素都会对应一个Fiber对象:

React Element树:       Fiber树:
    APP                 APP
     ↓                   ↓
    div                 div
    ↓ ↓                 ↓ ↓
   h1 h2               h1 h2

Fiber对象的结构如下:

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // Instance
  this.tag = tag; // 当前fiber的类型:ClassComponent、FunctionComponent、HostComponent...
  this.key = key; 
  this.elementType = null; // 类组件指向的是组件的类, HostComponent指向的是标签类型:div、span...
  this.type = null; // 与elementType一样,类组件指向的是组件的类, HostComponent指向的是标签类型:div、span...
  this.stateNode = null; // fiber的实例,类组件指向的是组件实例,HostComponent指向的是dom元素

  // Fiber 链表
  this.return = null; // 指向父级fiber
  this.child = null; // 指向子级fiber
  this.sibling = null; // 指向兄弟fiber
  this.index = 0;

  this.ref = null; // ref相关

  // 更新相关
  this.pendingProps = pendingProps; // 将要执行更新的属性
  this.memoizedProps = null; // 上一次更新的props
  this.updateQueue = null; // 更新队列
  this.memoizedState = null; // 上一次更新的state
  this.dependencies = null;

  this.mode = mode;

  // Effects
  this.flags = NoFlags;
  this.subtreeFlags = NoFlags;
  this.deletions = null;

  // 和lanes优先级相关
  this.lanes = NoLanes; // 表示当前节点是否需要更新
  this.childLanes = NoLanes; // 表示当前节点的子节点是否需要更新

  // current树和workInProgress树中节点相互关联的属性
  // 可以用于判断是否需要更新还是创建,有值表示更新,反之则需要创建
  this.alternate = null;
}

双缓冲树的概念

在React更新过程中会有两颗树,一颗树叫做current树,一颗树叫做workInprogress树。

current树对应了屏幕上已经渲染好的内容,workInprogress树是根据current树深度优先遍历出来新的fiber树,所有需要更新的内容都会在workInprogress树上体现。当更新未完成时,屏幕上始终都只会显示current树对应的内容,当更新完成则会将current树切换为workInprogress树,此时workInprogress树则变成新的current树。

构建Fiber树

下面我们根据源码,来看看是如何构建Fiber树的。

我们从入口函数开始:

ReactDOM.createRoot(root).render(<Demo1 />);

首先调用了createRoot函数,直接进入源码查看:

export function createRoot(
  container: Container,
  options?: CreateRootOptions,
): RootType {
  ...

  // 创建容器-Fiber根节点
  const root = createContainer(
    container,
    ConcurrentRoot,
    hydrate,
    hydrationCallbacks,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
  );
  ...

  return new ReactDOMRoot(root);
}

只看与创建Fiber相关的代码,可以看到调用了createContainer函数

export function createContainer(
  containerInfo: Container,
  tag: RootTag,
  hydrate: boolean,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  isStrictMode: boolean,
  concurrentUpdatesByDefaultOverride: null | boolean,
): OpaqueRoot {
  return createFiberRoot(
    containerInfo,
    tag,
    hydrate,
    hydrationCallbacks,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
  );
}

createContainer函数内部有调用了createFiberRoot函数

export function createFiberRoot(
  containerInfo: any,
  tag: RootTag,
  hydrate: boolean,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  isStrictMode: boolean,
  concurrentUpdatesByDefaultOverride: null | boolean,
): FiberRoot {

  // 创建fiber root根节点,在react项目中fiber root根节点只存在一个
  const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);

  // 创建root fiber节点,可以是多个
  const uninitializedFiber = createHostRootFiber(
    tag,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
  );

  // 关联fiber root和root fiber
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;

  ...

  // 初始化root fiber上的更新队列
  initializeUpdateQueue(uninitializedFiber);

  return root;
}

可以看到首先创建了一个FiberRootNode实例,然后又调用createHostRootFiber函数,这个函数的作用是创建了一个FiberNode的实例并返回,然后在FiberRoot实例上使用current属性关联了FiberNode实例,FiberNode实例上使用stateNode属性关联了FiberRoot实例,我们将FiberRootNode的实例叫做fiber root,FiberNode实例叫做root fiber,因为后续调用render方法渲染的内容都会挂载到root fiber下,所以我们可以把root fiber看做是render内容的根节点。

在react项目中fiber root根节点只存在一个。

它们的关系如下:

1632894306985.jpg

最后将fiber root返回,然后在createRoot中拿到返回的fiber root,将它作为参数创建了一个ReactDOMRoot实例

那我们来看一下ReactDOMRoot中的源码:

function ReactDOMRoot(internalRoot) {
  this._internalRoot = internalRoot;
}

ReactDOMRoot.prototype.render = function(children: ReactNodeList): void {
  const root = this._internalRoot; // fiber root

  ...
  updateContainer(children, root, null, null);
};

可以看到ReactDOMRoot中将fiber root赋值给了私有属性_internalRoot。

ReactDOMRoot的原型上有个render方法,也就是调用完createRoot方法后,链式调用的render方法: ReactDOM.createRoot(root).render(<Demo1 />);

可以看到render方法中调用了updateContainer函数,传入了children,也就是我们调用render方法时,传入的需要渲染的React element对象。

到这里基础的Fiber树就创建出来了。

后面就是开始创建任务,使用Scheduler进行任务调度,具体可以看我另二篇文章React源码解析之优先级Lane模型上 React源码解析之Scheduler

创建WorkInprogress

当任务开始执行时,会调用prepareFreshStack函数,而这个函数的作用则是创建workInprogress树:

function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
  ...
  workInProgress = createWorkInProgress(root.current, null);
  ...
}

可以这个函数中调用createWorkInProgress,将root.current作为参数传入,创建了workInProgress。root则是fiber root,root.current则是root fiber。

那我们进入createWorkInProgress函数中看一下,是如何创建workInProgress的:

export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  let workInProgress = current.alternate;
  if (workInProgress === null) {
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode,
    );
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;

    ...

    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    workInProgress.pendingProps = pendingProps;
    workInProgress.type = current.type;
    workInProgress.flags = NoFlags;

    workInProgress.subtreeFlags = NoFlags;
    workInProgress.deletions = null;
    ...
  }
  
  ...

  workInProgress.flags = current.flags & StaticMask;
  workInProgress.childLanes = current.childLanes;
  workInProgress.lanes = current.lanes;

  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue;

  const currentDependencies = current.dependencies;
  workInProgress.dependencies =
    currentDependencies === null
      ? null
      : {
          lanes: currentDependencies.lanes,
          firstContext: currentDependencies.firstContext,
        };

  workInProgress.sibling = current.sibling;
  workInProgress.index = current.index;
  workInProgress.ref = current.ref;

  ...
  return workInProgress;
}

可以看到,首先获取当前current树上fiber节点的alternate属性,也就是workInProgress上对应的fiber节点。如果workInProgress的fiber节点不为空,则直接复用,并会更新pendingProps,pendingProps也就是本次更新需要更新的内容。而为空则会重新创建一个新的fiber对象,并且与current树上的fiber节点使用alternate属性相互关联。

alternate的作用是可以在下一次更新的时候,判断alternate是否有值,有则说明是更新,无则是创建。

此时的Fiber树,是这样的:

1632898779363.jpg

Fiber的更新

在React更新的过程中,共分为两个阶段:

  1. render阶段
  2. commit阶段 render阶段是可以将中断的,而在commit阶段是不可中断的。

在render阶段每个Fiber节点又都会经历两个阶段:

  1. beginWork阶段
  2. completeWork阶段

beginWork阶段的主要工作,则是从root fiber开始向下深度遍历构建workInprogress树,进行diff算法确定需要更新的fiber的最终状态。

completeWork阶段主要工作,则是从深度遍历的最后一个节点开始向上回溯,并会检查是否有兄弟节点有则会再次执行beginWork流程,没有则会向上回溯。在回溯过程中,将需要更新fiber的属性收集起来挂载到fiber节点下的更新队列updateQueue上,并且添加更新的flags。每次回溯都将子节点的flags挂载到父节点的subtreeFlags上。

接下来将分别对beginWork和completeWork的源码解析写成单独的章节。