react fiber如何生成

2,073 阅读4分钟

一、前言

  在之前的一篇文章中react fiber概念及原理,介绍了react v16中fiber扮演了什么样的一个角色,以及它的工作原理。概念和原理只能让你有一个初步的认识,想要深入的了解fiber,那么必须要从源码入手,看看fiber具体的实现是怎样的。接下来的内容将结合思维导图和代码描述react fiber在代码中如何生成。

二、react三个阶段

  首先,react的设计理念是把整个应用看成是一个视图,当首屏渲染或者更新时,就会根据一系列过程生成新的视图,并渲染。这一系列过程伴随fiber的出现而发生了一些改变,react v16之前,react主要有两个阶段,分别是
  1.协调(Reconciler),根据状态的变化,找出变化的组件,并打上标记
  2.渲染(Renderder),根据被打上标记的组件,更新视图
  react v16及之后,react在原有两个阶段的基础上增加了一个调度过程(Scheduler),主要负责任务的调度(此文不涉及)
  而我们关心的fiber生成就是发生在在协调阶段(Reconciler)过程中,刚才我们说到,react在首屏渲染和更新时都会经历协调阶段,所以我们分首屏渲染和更新两种情况来看fiber生成过程。

三、首屏渲染时的fiber

  根据react文档,reactDom创建应用有三种模式,分别是

  1. legacy -- ReactDOM.render(, rootNode)
  2. blocking -- ReactDOM.createBlockingRoot(rootNode).render()
  3. concurrent -- ReactDOM.createRoot(rootNode).render()

  此次我们讨论是的legacy模式,也是目前react默认的模式,我先根据react官方文档运行demo,看一下首屏渲染时的调用栈

function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
  var root = container._reactRootContainer;
  var fiberRoot;

  if (!root) {
    // Initial mount
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
    fiberRoot = root._internalRoot;
    unbatchedUpdates(function () {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    //...do something
  }

  return getPublicRootInstance(fiberRoot);
}

  在legacyRenderSubtreeIntoContainer中,因为是首次渲染,所以会创建rootFiberNode和rootFiber,然后将rootFiber作为此次渲染的workInProgress fiber,也就是一下renderRootSync函数中的第一个参数
  renderRootSync也就是开始创建fiber的入口函数

function renderRootSync(root, lanes) {
  //...do something
  do {
    try {
      workLoopSync();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);
  //...do something
  return workInProgressRootExitStatus;
}

function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

function performUnitOfWork(unitOfWork) {
  var current = unitOfWork.alternate;
  setCurrentFiber(unitOfWork);
  var next;

  if ( (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
  }

  resetCurrentFiber();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;

  if (next === null) {
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

  ReactCurrentOwner$2.current = null;
}

  从代码上看react会执行一次循环,最终会运行performUnitOfWork这个函数,上面代码看着比较难懂,这里结合图来理解一下。我们知道fiber是类似于链表的数据结构,那么reactDom其实是通过递归的方式生成fiber,递阶段就是beginWork(最终调用createFiberFromElement返回一个fiber节点),归阶段就是completeWork(生成dom并挂载在父节点,更新处理props以及生成副作用链表),我们用图解的方式来表示一下整个递归的过程,假设我们的jsx是这样的结构

<div>
    <header>
    	<span>test</span>
        这是一个头
    </header>
    <p>这也是一段话</p>
</div>

那么它对应的fiber数据就是

所以整个过程就是:

  1. 递阶段(beginWork)依次生成div、header、span的fiber,span没有child,所以进行2流程
  2. 归阶段(completeWork)处理span、header,生成dom,赋值为stateNode属性,并挂载到父节点的dom上,header有一个sibling节点,所以进行3流程
  3. 递阶段(beginWork)生成p的fiber,p没有child,所以进行4流程
  4. 归阶段(completeWork)处理p、div,rootFiber生成dom,赋值为stateNode属性,并挂载到父节点的dom上 如此可以得到最终的一个fiber数据结构,且应用的根节点其中rootFiber已经有了一个完整的dom树,然后进行渲染(Renderer)

四、更新时的fiber

  上面说到react的设计理念是将整个应用看做是一个视图,所以reactDom在开始Reconciler时都是以rootFiber为起点。在更新时,当rooFiber的alternate不存在时,会克隆一个current fiber作为此次的workInProgress fiber,当rootFiber的alternate存在时,就将其作为此次更新的workInProgress。然后同样的进行上面的描述的流程。不同的是这次递归会在递阶段根据需要更新组件的jsx和workInProgress fiber进行对比生成新的fiber,也就是大名鼎鼎的diff算法。(alternate在此文中介绍react fiber概念及原理