react原理:render阶段整体认识

1,029 阅读3分钟

点击这里进入react原理专栏

ReactDOM.render这篇文章中提到过react更新流程中的render阶段。render阶段这个名字,很容易让人们认为这是react会在这个阶段渲染页面,其实不然,render阶段是react构建workInProgress树的阶段。这篇文章会整体介绍一下render所做的工作。

这篇文章中提到了无论是初次渲染还是后续的页面更新,都会调用performSyncWorkOnRoot。这个方法会调用renderRootSync,也就是render阶段的入口

function renderRootSync(root, lanes) {
  var prevExecutionContext = executionContext;
  // 设置render阶段的标志
  executionContext |= RenderContext;
  var prevDispatcher = pushDispatcher();

  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    prepareFreshStack(root, lanes);
    startWorkOnPendingInteractions(root, lanes);
  }

  do {
    try {
      workLoopSync();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);
  // 。。。
  return workInProgressRootExitStatus;
}

renderRootSync中有两个比较重要的部分:prepareFreshStackworkLoopSync。首先看一下prepareFreshStack

function prepareFreshStack(root, lanes) {
 // ...
 // root就是fiberRoot
 workInProgress = createWorkInProgress(root.current, null); 
 // ...
}

这里修改了全局变量workInProgress,而createWorkInProgress会根据workInProgress是否存在,初始化一个workInProgress树的rootFiber

function createWorkInProgress(current, pendingProps) {
  var workInProgress = current.alternate;
  if (workInProgress === null) {
    // 初次渲染,新建一个fiber对象并初始化
  } else {
    // 后续更新,重置workInProgress
  }
}

现在,准备工作结束后,进入workLoopSyncworkLoopSync很简单,就是一个循环

function workLoopSync() {
  while (workInProgress !== null) {
    // 全局变量workInProgress
    performUnitOfWork(workInProgress);
  }
}

performUnitOfWork就是对单个fiber节点的处理,该方法会调用beginWorkcompleteUnitOfWork来完成对一个fiber节点的处理。接下来先说一下fiber树的遍历流程。

render阶段采用深度优先遍历的方式,初次遍历到该节点时调用beginWork处理,后续返回该节点时调用completeUnitOfWork处理,而全局变量workInProgress就指向被访问到的节点。

注意:全局变量workInProgress是一个指针,会随着fiber树的遍历而变化,并不是workInProgress树的根节点

render阶段遍历图.jpg

执行顺序如下:

rootFiber beginWork
App beginWork
div1 beginWork
span1 beginWork
span1 completeUnitOfWork
div2 beginWork
span2 beginWork
span2 completeUnitOfWork
div2 completeUnitOfWork
div1 completeUnitOfWork
App completeUnitOfWork
rootFiber completeUnitOfWork

知道了render阶段的遍历流程,下面看一下beginWorkcompleteUnitOfWork都做了哪些工作

beginWork

首先,beginWork会根据当前fiber的优先级是否足够,来决定是否复用已有的结构,具体逻辑在这里

if (current !== null) {
  // 当前fiber已经挂载过了,现在是后续更新阶段
  var oldProps = current.memoizedProps;
  var newProps = workInProgress.pendingProps;

  if (oldProps !== newProps || hasContextChanged() || (
   workInProgress.type !== current.type )) {
    didReceiveUpdate = true;
  } else if (!includesSomeLane(renderLanes, updateLanes)) {
    didReceiveUpdate = false; 
    switch (workInProgress.tag) {
     // 针对不同类型组件的处理   
    }
    // 复用已有结构
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  } else {
    if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
      didReceiveUpdate = true;
    } else {
      didReceiveUpdate = false;
    }
  }
} else {
  didReceiveUpdate = false;
}

而重点则是后面的switch语句

switch (workInProgress.tag) {
  case IndeterminateComponent: 
    // ...
  case LazyComponent: 
    // ...
  case FunctionComponent: 
    // ...
  case ClassComponent: 
    // ...
}

当不能直接复用已有结构时,会根据不同组件fiber类型,进入不同的更新逻辑。之后的文章会主要介绍类组件和函数组件时如何创建的更新的。现在先介绍一下beginWork会完成哪些工作:对于类组件与函数组件而言,beginWork会计算更新之后的状态,并执行reconcileChildren方法,也就是我们常说的diff阶段。在diff过程中,会根据current树来创建对应的workInProgress树节点,并根据更新前后的渲染结果,为fiber打上effect,来说明该fiber是更新,新增还是删除,最终,beginWork会返回workInProgress.child,也就是当前处理的fiber节点的第一个子节点。

completeUnitOfWork

workInProgress指针来到fiber树的叶子节点时,就会执行completeUnitOfWork方法,这个方法主要会根据fiber节点的创建对应的dom节点,并将其插入到自己的父节点中,并且收集fibereffect,创建一个effectList链表,为commit阶段使用。

这篇文章介绍了render阶段的整体流程,后续会为大家介绍类组件和函数组件时如何挂载和更新的,也就是setStatehooks的原理。