React应用mount时全流程解析

967 阅读11分钟

引言

前端框架ReactVue以及Angular已经三分天下数年。Angular因为版本升级造成的困扰,在国内已经几乎淡出前端人员的视野。可以说在国内是ReactVue在平分天下。而用过React的人都觉得它要比Vue香,它的声明式UI、用JavaScript编写组件间逻辑的灵活性以及大型应用渲染的速度都胜于Vue

React应用在mount时到底做了什么呢?本文从源码角度来解析它的执行逻辑和架构。

Top Level API

我们开发中必然少不了如下代码

import React, { Component } from 'react';
class Comp extends Component {
  onChange = () => {
    this.setState(...)
  }
  render () {
    return ...
  }
}

React提供给我们的Top Level API 到目前的V17版本,一直都没变过。只是在V16.8时引入的HOOks的概念,但是并不影响我们开发。

这些Top Level API是如何实现的?

我们看一段React.Component的源码:

本文所有源码以V17.01版本为准。

// 本段源码在文件 packages/react/src/ReactBaseClasses.js 中
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // 省略注释
  this.refs = emptyObject;
  // 省略注释
  this.updater = updater || ReactNoopUpdateQueue;
}

Component.prototype.isReactComponent = {};

// 省略注释
Component.prototype.setState = function(partialState, callback) {
  // ...省略部分代码
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
// 省略注释
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

可见,我们经常使用的this.setState修改状态其实是执行了,updater.enqueueSetState方法。updater默认是ReactNoopUpdateQueue对象。

// 本段源码在文件 packages/react/src/ReactNoopUpdateQueue.js 中
/**
 * 这是一个抽象的API
 */
const ReactNoopUpdateQueue = {
  // 省略注释
  isMounted: function(publicInstance) {
    return false;
  },

  // 省略注释
  enqueueForceUpdate: function(publicInstance, callback, callerName) {
    warnNoop(publicInstance, 'forceUpdate');
  },

  // 省略注释
  enqueueReplaceState: function(
    publicInstance,
    completeState,
    callback,
    callerName,
  ) {
    warnNoop(publicInstance, 'replaceState');
  },

  // 省略注释
  enqueueSetState: function(
    publicInstance,
    partialState,
    callback,
    callerName,
  ) {
    warnNoop(publicInstance, 'setState');
  },
};

这下,我们清晰明了Top Level APIReact中只是抽象层的API,并没有实现具体的功能。

那他具体的功能是在哪实现的呢?这个我们先卖个关子,并命名为 关子1⃣️

其实Hooks也是这么设计的。我们平时使用的

import React, { useState } from 'react';

这些钩子函数在React中也都是抽象接口,并没有具体的功能。

感兴趣的可以去文件 packages/react/src/ReactHooks.js 中看看各个Hook的抽象实现。

render方法

我们都知道React渲染的入口是

ReactDOM.render(<App />, document.getElementById('root'));

我们就看看render方法都做了什么。

render方法主要做了以下几件事:

  • 给DOM对象root挂载 _reactRootContainer 私有属性
  • _reactRootContainer私有属性上挂载 _internalRoot 属性即FiberRootNode对象
  • 创建HostRootFiber对象
  • 赋值FiberRootNode对象的current指向HostRootFiber,赋值HostRootFiberstateNode指向FiberRootNode
  • 非批量更新模式下调用updateContainer方法生成完整的Fiber树

FiberRootNodeHostRootFiber(也叫RootFiber)的关系如图:

1.png

FiberRootNode

FiberRootNode也可以叫FiberRoot是整个应用的起点。

它也是一个JavaScript对象,承载了应用更新过程中的信息。

比如ContainerInfo是渲染容器的根结点即render的第二个参数。

current是当前应用的Fiber对象,即是RootFiber

finishedWork是已经完成任务的Fiber对象,在commit阶段会处理它。

还有一些xxxxLines属性是和优先级有关。

HostRootFiber

HostRootFiber也叫RootFiberFiber对象,它是Fiber树的根节点。

FiberReact的核心,它可以理解为虚拟DOM

它主要有以下两层含义:

1、从静态的数据结构来说,每个Fiber节点对应一个React Element,保存组件的类型和对应的DOM节点信息

2、从动态的工作单元来说,每个Fiber节点保存更新状态下组件改变的状态以及DOM最终需要进行操作的行为比如被删除、被插入到页面、被更新属性等

我们看看Fiber对象的属性:

// 本段源码在文件 packages/react-reconciler/src/ReactFiber.old.js 中
function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  /** --- 静态数据结构的属性 --- */
  // 对应的组件类型 Function/Class/Host...
  this.tag = tag;
  // 即jsx的key属性
  this.key = key;
  // 除特殊情况外,大部分情况与type相同
  this.elementType = null;
  // 如果是函数组件它是函数本身,如果是类组件它是类对象,如果是HostComponent它的DOM节点
  this.type = null;
  // 对应的真实DOM节点
  this.stateNode = null;

  /** --- 用于连接其他fiber节点形成fiber树 --- */
  // 父节点
  this.return = null;
  // 第一个子节点
  this.child = null;
  // 兄弟节点
  this.sibling = null;
  this.index = 0;

  this.ref = null;

  /** --- 作为动态工作单元的属性 --- */
  // 更新的props
  this.pendingProps = pendingProps;
  // 老的props
  this.memoizedProps = null;
  // 更新组成的链表,比如同时调用多个setState时
  this.updateQueue = null;
  // 老的state以及计算后的形成的新的state
  this.memoizedState = null;
  this.dependencies = null;

  this.mode = mode;

  /** --- 本次更新造成的DOM操作 --- */
  this.flags = NoFlags;
  this.nextEffect = null;

  this.firstEffect = null;
  this.lastEffect = null;
  this.subtreeFlags = NoFlags;
  this.deletions = null;
  // 调度优先级相关
  this.lanes = NoLanes;
  this.childLanes = NoLanes;
  // 该fiber在另一次更新时对应的fiber
  this.alternate = null;
  // ...省略部分代码
}

从上面代码中我们可以知道,每个Fiber节点都对应一个React Element,多个Fiber节点如何构建Fiber树的呢?就是依赖下面三个属性:

// 父节点
this.return = null;
// 第一个子节点
this.child = null;
// 兄弟节点
this.sibling = null;

我们举个例子:

function App() {
 reutrn (
 	<div>
   	<p>闹闹前端</p>
        <ul>
    	 <li>javascript</li>
         <li>nodejs</li>
    </ul>
  </div>
 )
}

对应的Fiber树结构:

2.png

好,现在对FiberRootNodeHostRootFiber以及Fiber树有了了解。

接下来就正式进入到Fiber树的创建过程即Reactrender阶段,即Fiber树的创建过程以及DOM树的构建过程。

render阶段

render阶段是Fiber树的创建过程以及DOM树的构建过程,不要和上面说的ReactDOM.render方法搞混了。

render阶段主要执行了两个方法即beginWorkcompleteUnitOfWork

先简单说一下,进入beginWork之前的函数调用路线。

beginWork前

ReactDOM.render方法中会 非批量更新模式下调用updateContainer方法生成完整的Fiber


// 本段源码在文件 packages/react-reconciler/src/ReactFiberReconciler.old.js 中
export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {
  // ... 省略部分代码

  enqueueUpdate(current, update);
  scheduleUpdateOnFiber(current, lane, eventTime);

  return lane;
}

updateContainer方法内调用scheduleUpdateOnFiber方法。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
export function scheduleUpdateOnFiber(
  fiber: Fiber,
  lane: Lane,
  eventTime: number,
) {
  // ... 省略部分代码
  if (lane === SyncLane) {
    if (
      // Check if we're inside unbatchedUpdates
      (executionContext & LegacyUnbatchedContext) !== NoContext &&
      // Check if we're not already rendering
      (executionContext & (RenderContext | CommitContext)) === NoContext
    ) {
      // ... 省略部分代码
      performSyncWorkOnRoot(root);
    } else {
      // ... 省略部分代码
    }
  } else {
    // ... 省略部分代码
  }

  // ... 省略部分代码
}

scheduleUpdateOnFiber方法内调用performSyncWorkOnRoot方法。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function performSyncWorkOnRoot(root) {
  // ... 省略部分代码
  let lanes;
  let exitStatus;
  if (
    root === workInProgressRoot &&
    includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)
  ) {
    // ... 省略部分代码
  } else {
    lanes = getNextLanes(root, NoLanes);
    exitStatus = renderRootSync(root, lanes);
  }

  // ... 省略部分代码

  // 此段代码进入commit阶段
  const finishedWork: Fiber = (root.current.alternate: any);
  root.finishedWork = finishedWork;
  root.finishedLanes = lanes;
  commitRoot(root);

  // Before exiting, make sure there's a callback scheduled for the next
  // pending level.
  ensureRootIsScheduled(root, now());

  return null;
}

performSyncWorkOnRoot方法内部执行renderRootSync方法。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function renderRootSync(root: FiberRoot, lanes: Lanes) {

  // ... 省略部分代码

  do {
    try {
      workLoopSync();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);
  // ... 省略部分代码

  return workInProgressRootExitStatus;
}

renderRootSync方法内部调用workLoopSync方法。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

这是个循环,workInProgress即为当前执行的Fiber

// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function performUnitOfWork(unitOfWork: Fiber): void {
  const current = unitOfWork.alternate;
  setCurrentDebugFiberInDEV(unitOfWork);

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

  resetCurrentDebugFiberInDEV();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

  ReactCurrentOwner.current = null;
}

这里先调用beginWork,后调用completeUnitOfWork

我们先关注一下beginWork的参数

  • currentunitOfWork.alternate即为当前fiber节点上一次更新后的fiber节点可以理解为老fiber节点
  • unitOfWork既是workInProgress,它是当前组件对应的fiber节点
  • SubtreeRenderLanes和优先级有关。本篇不涉及到优先级,所以这里忽略。

beginWork

beiginWork会根据传入的current是否存在,区别是mount还是update渲染。

mount时,除了FiberRootNode外,其他的fiber节点的alternate肯定是null

它会根据fiber.tag的不同,创建不同的子fiber节点。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberBeginWork.old.js 中
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  const updateLanes = workInProgress.lanes;

  // ...省略部分代码
  
  // current存在即为update更新渲染,可以做一些优化比如复用current节点
  if (current !== null) {
      // ...省略部分代码
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    } else {
      // ...省略部分代码
  } else {
    didReceiveUpdate = false;
  }
  // ...省略部分代码
  
  // current === null即mount时
  switch (workInProgress.tag) {
    // ...省略部分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);
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
    // ...省略部分tag类型的处理代码
    }
  }
  // ...省略部分代码
}

可以看出,根据workInProgress.tag类型调用相应的updateXXX方法进行相应组件类型的处理。具体的tag类型可以参考文件 packages/react-reconciler/src/ReactWorkTags.js

我们以ClassComponent为例。

const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
      workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
  current,
  workInProgress,
  Component,
  resolvedProps,
  renderLanes,
);

先解析defaultProps,然后调用updateClassComponent方法。

updateClassComponent

// 本段源码在文件 packages/react-reconciler/src/ReactFiberBeginWork.old.js 中
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) {
      // ...省略部分代码
    }
    // mount时,执行
    constructClassInstance(workInProgress, Component, nextProps);
    mountClassInstance(workInProgress, Component, nextProps, renderLanes);
    shouldUpdate = true;
  } else if (current === null) {
    // ...省略部分代码
  } else {
    // ...省略部分代码
  }
  const nextUnitOfWork = finishClassComponent(
    current,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderLanes,
  );
  // ...省略部分代码
  return nextUnitOfWork;
}

mount时执行constructClassInstance即使用new运算符实例化此类组件同时设定累的updater对象,mountClassInstance调用前期的生命周期方法比如componentWillMount以及静态方法getDerivedStateFromProps

接着调用finishClassComponent,它调用类对象上的render方法,进入Reconciler阶段,根据React Element创建对应的Fiber节点,并返回这个节点作为下一个执行的单元任务。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberClassComponent.old.js 中
function constructClassInstance(
  workInProgress: Fiber,
  ctor: any,
  props: any,
): any {
  // ...省略部分代码

  // 实例化类组件
  const instance = new ctor(props, context);
  const state = (workInProgress.memoizedState =
    instance.state !== null && instance.state !== undefined
      ? instance.state
      : null);
  adoptClassInstance(workInProgress, instance);

  // ...省略部分代码

  return instance;
}

这里的关键是adoptClassInstance方法。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberClassComponent.old.js 中
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
  instance.updater = classComponentUpdater;
  workInProgress.stateNode = instance;
  // ...省略部分代码
}

这里给实例对象挂载updater属性,对应的值是classComponentUpdater,这个对象就是我们在开篇说Component时setState方法的具体实现了。也就是关子1⃣️的答案。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberClassComponent.old.js 中
const classComponentUpdater = {
  isMounted,
  enqueueSetState(inst, payload, callback) {
    const fiber = getInstance(inst);
    const eventTime = requestEventTime();
    const lane = requestUpdateLane(fiber);
    // 创建更新
    const update = createUpdate(eventTime, lane);
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'setState');
      }
      update.callback = callback;
    }
    // 构建更新的链表
    enqueueUpdate(fiber, update);
    // 进入更新逻辑,回归beginWork
    scheduleUpdateOnFiber(fiber, lane, eventTime);

    // ...省略部分代码
  },
  enqueueReplaceState(inst, payload, callback) {
    // ...省略代码
  },
  enqueueForceUpdate(inst, callback) {
    // ...省略代码
  },
};

当我们在类组件里执行this.setState时,便会执行enqueueSetState方法。

enqueueSetState先创建update对象,然后构建updateQueue链表,最后执行scheduleUpdateOnFiber方法,回归到beginWork的更新状态。

我们接着看finishClassComponent方法吧。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberBeginWork.old.js 中
function finishClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  shouldUpdate: boolean,
  hasContext: boolean,
  renderLanes: Lanes,
) {
  // ...省略部分代码

  const instance = workInProgress.stateNode;

  ReactCurrentOwner.current = workInProgress;
  let nextChildren;
  if (
    didCaptureError &&
    typeof Component.getDerivedStateFromError !== 'function'
  ) {
    // ...省略部分代码
  } else {
    if (__DEV__) {
      // ...省略部分代码
    } else {
      // 调用实例对象的render方法,获取React Element对象
      nextChildren = instance.render();
    }
  }

  workInProgress.flags |= PerformedWork;
  if (current !== null && didCaptureError) {
    // ...省略部分代码
  } else {
    // 进入调和阶段,这是Reconciler的核心模块
    reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  }

  workInProgress.memoizedState = instance.state;

  // ...省略部分代码

  return workInProgress.child;
}

重点关注instance.render方法即我们书写在类组件里的render方法,它返回的是jsx。我们都知道React会把jsx转化为React Element对象。nextChildren就是转化后的React Element对象,reconcileChildren就是对这些对象进行调和的。

经过reconcileChildren后,便会赋值workInProgresschild对象。

reconcileChildren

函数reconcileChildren在文件 packages/react-reconciler/src/ReactFiberBeginWork.old.js 中,它同样根据current是否为null区分是mount还是update。如果是mount调用mountChildFibers方法,否则调用reconcileChildFibers方法。这里我们只看mountChildFibers方法。

// 本段源码在文件 packages/react-reconciler/src/ReactChildFiber.old.js 中
function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    lanes: Lanes,
  ): Fiber | null {
    // ...省略部分代码

    // Handle object types
    const isObject = typeof newChild === 'object' && newChild !== null;

    if (isObject) {
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE:
          return placeSingleChild(
            reconcileSingleElement(
              returnFiber,
              currentFirstChild,
              newChild,
              lanes,
            ),
          );
        case REACT_PORTAL_TYPE:
          return placeSingleChild(
            reconcileSinglePortal(
              returnFiber,
              currentFirstChild,
              newChild,
              lanes,
            ),
          );
        case REACT_LAZY_TYPE:
          if (enableLazyElements) {
            const payload = newChild._payload;
            const init = newChild._init;
            // TODO: This function is supposed to be non-recursive.
            return reconcileChildFibers(
              returnFiber,
              currentFirstChild,
              init(payload),
              lanes,
            );
          }
      }
    }

    if (typeof newChild === 'string' || typeof newChild === 'number') {
      return placeSingleChild(
        reconcileSingleTextNode(
          returnFiber,
          currentFirstChild,
          '' + newChild,
          lanes,
        ),
      );
    }

    if (isArray(newChild)) {
      return reconcileChildrenArray(
        returnFiber,
        currentFirstChild,
        newChild,
        lanes,
      );
    }

    // ...省略部分代码

    return deleteRemainingChildren(returnFiber, currentFirstChild);
  }

  return reconcileChildFibers;
}

可以看到它根据不同类型,调用不同方法调和节点。我们先看调和单个节点的实现即reconcileSingleElement方法

// 本段源码在文件 packages/react-reconciler/src/ReactChildFiber.old.js 中
function reconcileSingleElement(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    element: ReactElement,
    lanes: Lanes,
  ): Fiber {
    const key = element.key;
    let child = currentFirstChild;
    while (child !== null) {
      // 这里是更新时执行
      // ...省略部分代码
    }

    if (element.type === REACT_FRAGMENT_TYPE) {
      const created = createFiberFromFragment(
        element.props.children,
        returnFiber.mode,
        lanes,
        element.key,
      );
      created.return = returnFiber;
      return created;
    } else {
      const created = createFiberFromElement(element, returnFiber.mode, lanes);
      created.ref = coerceRef(returnFiber, currentFirstChild, element);
      created.return = returnFiber;
      return created;
    }
  }

mount时到这,就根据element.type类型创建当前的fiber节点即createFiberFromElement方法和createFiberFromFragment。并赋值新节点的return指针。

我们再看看多节点的调和即reconcileChildrenArray方法

// 本段源码在文件 packages/react-reconciler/src/ReactChildFiber.old.js 中
function reconcileChildrenArray(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChildren: Array<*>,
    lanes: Lanes,
  ): Fiber | null {
    // ...省略部分代码
    let resultingFirstChild: Fiber | null = null;
    let previousNewFiber: Fiber | null = null;

		// mount时oldFiber为null
    let oldFiber = currentFirstChild;
    let lastPlacedIndex = 0;
    let newIdx = 0;
    let nextOldFiber = null;
    for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
      // ...更新是逻辑
    }

    if (newIdx === newChildren.length) {
      deleteRemainingChildren(returnFiber, oldFiber);
      return resultingFirstChild;
    }

    if (oldFiber === null) {
      for (; newIdx < newChildren.length; newIdx++) {
        // 创建子节点
        const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
        if (newFiber === null) {
          continue;
        }
        lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
        if (previousNewFiber === null) {
          // 保存第一个子节点
          resultingFirstChild = newFiber;
        } else {
          // 把上一个子节点的sibling指向当前新创建的子节点
          previousNewFiber.sibling = newFiber;
        }
        // 保存当前子节点为上一个子节点
        previousNewFiber = newFiber;
      }
      // 返回第一个子节点
      return resultingFirstChild;
    }

    // ...省略更新是逻辑

    return resultingFirstChild;
  }

mount时,从数组第一项开始遍历,创建第一个子节点时保存在resultingFirstChild上。

接着如果previousNewFibernull也就是创建第一个子节点,把第一个子节点赋值给它,之后创建第二子节点时就可以执行previousNewFiber.sibling=newFiber即把第二子节点赋值给它的sibling指针,然后在把它指向第二子节点,这样第一个子节点的sibling就指向了第二个子节点。逐渐遍历下去,就有第二个子节点的sibling指向第三个子节点,第三个子节点的sibling指向第四个子节点。最后返回resultingFirstChild即第一个子节点。

返回的第一个子节点会在reconcileChildren方法中赋值给workInProgress.child

这就是构建fiber树的具体过程。可以回头看看上面的Fiber树的图。

当然这里省略了更新时的调和也就是我们说的DOM Diff。因为本篇重点在mount的过程,所以这里不做解析。

performUnitOfWork方法中,如果beinWork返回的结果为null就进入completeUnitOfWork方法处理当前的fiber节点。

beginWork返回的是Fiber树的第一个子节点,也就是当前的深度遍历到底了。

completeUnitOfWork

beinWork逻辑里我们只看到了创建Fiber树的过程,并没有看到如何创建真实DOM

completeUnitOfWork就是创建真实DOM的过程,并会遍历fiber树创建DOM树。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function completeUnitOfWork(unitOfWork: Fiber): void {
  
  let completedWork = unitOfWork;
  do {
    // ...省略部分代码
    if ((completedWork.flags & Incomplete) === NoFlags) {
      // ...省略部分代码
      if (
        !enableProfilerTimer ||
        (completedWork.mode & ProfileMode) === NoMode
      ) {
        next = completeWork(current, completedWork, subtreeRenderLanes);
      } else {
        // ...省略部分代码
        next = completeWork(current, completedWork, subtreeRenderLanes);
        // ...省略部分代码
      }
      resetCurrentDebugFiberInDEV();

      if (next !== null) {
        
        workInProgress = next;
        return;
      }
      // ...省略部分代码
    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      workInProgress = siblingFiber;
      return;
    }
    completedWork = returnFiber;

    workInProgress = completedWork;
  } while (completedWork !== null);
  // ...省略部分代码
}

此方法会调用completeWork方法,它会根据fiber.tag调用不同的处理逻辑,然后返回结果。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberCompleteWork.old.js 中
function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  const newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
    // ...省略部分类型的处理逻辑
    case HostComponent: {
      popHostContext(workInProgress);
      const rootContainerInstance = getRootHostContainer();
      const type = workInProgress.type;
      if (current !== null && workInProgress.stateNode != null) {
				// ...省略部分代码
      } else {
				// ...省略部分代码
        if (wasHydrated) {
          // ...省略部分代码
        } else {
          // 创建真实DOM
          const instance = createInstance(
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          );
					// ...省略部分代码
          // 添加子DOM到父DOM中
          appendAllChildren(instance, workInProgress, false, false);

          workInProgress.stateNode = instance;

          if (
            // 处理DOM的属性
            finalizeInitialChildren(
              instance,
              type,
              newProps,
              rootContainerInstance,
              currentHostContext,
            )
          ) {
            markUpdate(workInProgress);
          }
        }
				// ...省略部分代码
      }
      // ...省略部分代码
      return null;
    }
    case HostText: {
      // ...省略部分代码
      return null;
    }
    // ...省略类型处理逻辑
  }
  // ...省略部分代码
}

如果completeWork方法返回null就会判断有没有sibling,如果有则返回,再次进入beginWork逻辑;如果没有就会把它的return属性赋值给当前的workInProgress就进入到父节点的completeWork过程,从而创建父节点对应的真实DOM并添加子DOM。方法中的createInstance就是创建真实DOMappendAllChildren方法是添加子DOM的过程。

方法createInstance在文件packages/react-dom/src/client/ReactDOMHostConfig.js中。

方法appendAllChildren在文件packages/react-reconciler/src/ReactFiberCompleteWork.old.js中。

大家可以自己去看看。

我们以下面为例,一步步解析

function App() {
 reutrn (
 	<div>
   	<p>闹闹前端</p>
    <ul>
    	 <li>javascript</li>
       <li>nodejs</li>
    </ul>
  </div>
 )
}

第一步进入divbeginWork,它的子节点是Arraypul,所以会构建div对应的fiber节点(便于说明我们叫它Fiber_Div )的childp(Fiber_p),Fiber_psiblingul(Fiber_ul)然后返回Fiber_Divchild作为下一个工作单元。

第二步即为Fiber_p进入beginWork,会调用updateHostComponent方法,调和它的子节点即文本节点闹闹前端,返回null

第三步进入completeUnitOfWork方法,unitOfWork即为Fiber_p。然后调用completeWork方法,创建真实p DOM,并返回null。经过一些处理后,获取它的sibling,如果它有sibling就直接返回,在performUnitOfWork中重新赋值给next,再返回next。再进入beginWork阶段,这时就是调和Fiber_ul了。

第四步对Fiber_ul进行第一步到第三步的深度遍历。包括它的子节点li

第五步当进入第二个li的completeUninOfWork后,它的siblingnull。这时就会执行

completedWork = returnFiber;
workInProgress = completedWork;

它的returnFiber就是Fiber_ul,此时completedWork不为null,即进入while的第二次循环。

第六步进行Fiber_ulcompleteWork,执行创建ul的真实DOM,并执行appendAllChildren方法,遍历它的child指针添加到真实的DOM中,从而构建真实的ulliDOM关系树。

第七步对Fiber_div执行同样的逻辑,创建真实的div DOM和以及它和pulDOM关系树。

至此渲染阶段及render阶段结束。下面进入commit阶段。

commit阶段

方法commitRootcommit阶段的入口。它执行commitRootImpl方法。

方法commitRootImpl的工作主要分为三部分:

  • 执行commitBeforeMutationEffects方法即执行DOM操作前

  • 执行commitMutationEffects方法即执行DOM操作,把DOM渲染到页面

  • 执行commitLayoutEffects方法即执行DOM操作后

// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function commitRootImpl(root, renderPriorityLevel) {
  // ...省略部分代码

  const finishedWork = root.finishedWork;
  const lanes = root.finishedLanes;

  // ...省略部分代码
  root.finishedWork = null;
  root.finishedLanes = NoLanes;

  // ...省略部分代码

  if (firstEffect !== null) {
    // ...省略部分代码
    nextEffect = firstEffect;
    do {
      if (__DEV__) {
        // ...省略部分代码
      } else {
        try {
          commitBeforeMutationEffects();
        } catch (error) {
          // ...省略部分代码
        }
      }
    } while (nextEffect !== null);

    // ...省略部分代码
    nextEffect = firstEffect;
    do {
      if (__DEV__) {
        // ...省略部分代码
      } else {
        try {
          commitMutationEffects(root, renderPriorityLevel);
        } catch (error) {
          // ...省略部分代码
        }
      }
    } while (nextEffect !== null);

    // ...省略部分代码
    nextEffect = firstEffect;
    do {
      if (__DEV__) {
        // ...省略部分代码
      } else {
        try {
          commitLayoutEffects(root, lanes);
        } catch (error) {
          // ...省略部分代码
        }
      }
    } while (nextEffect !== null);

    nextEffect = null;

    // ...省略部分代码
  } else {
    // ...省略部分代码
  }

  // ...省略部分代码

  return null;
}

commitBeforeMutationEffects

// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function commitBeforeMutationEffects() {
  while (nextEffect !== null) {
    const current = nextEffect.alternate;

    if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
      // ... focus blur逻辑
    }

    const flags = nextEffect.flags;
    if ((flags & Snapshot) !== NoFlags) {
      setCurrentDebugFiberInDEV(nextEffect);
			// 类组件调用getSnapshotBeforeUpdate生命周期方法,HostRoot类型清空容器
      commitBeforeMutationEffectOnFiber(current, nextEffect);

      resetCurrentDebugFiberInDEV();
    }
    // 调度useEffect
    if ((flags & Passive) !== NoFlags) {
      // If there are passive effects, schedule a callback to flush at
      // the earliest opportunity.
      if (!rootDoesHavePassiveEffects) {
        rootDoesHavePassiveEffects = true;
        scheduleCallback(NormalSchedulerPriority, () => {
          flushPassiveEffects();
          return null;
        });
      }
    }
    nextEffect = nextEffect.nextEffect;
  }
}

整体功能分为三部分

  • 处理DOMfocusblur逻辑
  • 调用类组件的getSnapshotBeforeUpdate生命周期方法
  • 调度useEffect

commitMutationEffects

// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
  // 遍历effect链表
  while (nextEffect !== null) {
    setCurrentDebugFiberInDEV(nextEffect);

    const flags = nextEffect.flags;

    if (flags & ContentReset) {
      commitResetTextContent(nextEffect);
    }
		// 更新ref
    if (flags & Ref) {
      const current = nextEffect.alternate;
      if (current !== null) {
        commitDetachRef(current);
      }
      if (enableScopeAPI) {
        if (nextEffect.tag === ScopeComponent) {
          commitAttachRef(nextEffect);
        }
      }
    }
		// 根据effectTag不同,执行不同的逻辑处理
    const primaryFlags = flags & (Placement | Update | Deletion | Hydrating);
    outer: switch (primaryFlags) {
      // 插入DOM
      case Placement: {
        commitPlacement(nextEffect);
        nextEffect.flags &= ~Placement;
        break;
      }
      // 插入DOM并更新DOM
      case PlacementAndUpdate: {
        // Placement
        commitPlacement(nextEffect);
        nextEffect.flags &= ~Placement;

        // Update
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
        
      // ...省略SSR逻辑
      
      // 更新DOM  
      case Update: {
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      // 删除DOM
      case Deletion: {
        const deletedChild = nextEffect;
        const deletions = deletedChild.deletions;
        if (deletions !== null) {
          for (let i = 0; i < deletions.length; i++) {
            const deletion = deletions[i];
            deletion.flags &= ~Deletion;
            deletion.deletions = null;
            commitDeletion(root, deletion, renderPriorityLevel);
          }
        }
        break;
      }
    }

    resetCurrentDebugFiberInDEV();
    nextEffect = nextEffect.nextEffect;
  }
}

此过程会根据effectTag,对每个Fiber节点进行增删改的操作。

方法commitPlacement 是插入DOM

// 本段源码在文件 packages/react-reconciler/src/ReactFiberCommitWork.old.js 中
function commitPlacement(finishedWork: Fiber): void {
  if (!supportsMutation) {
    return;
  }

  const parentFiber = getHostParentFiber(finishedWork);

  let parent;
  let isContainer;
  // 获取父级DOM节点
  const parentStateNode = parentFiber.stateNode;
  switch (parentFiber.tag) {
    case HostComponent:
      parent = parentStateNode;
      isContainer = false;
      break;
    case HostRoot:
      parent = parentStateNode.containerInfo;
      isContainer = true;
      break;
    case HostPortal:
      parent = parentStateNode.containerInfo;
      isContainer = true;
      break;
    case FundamentalComponent:
      if (enableFundamentalAPI) {
        parent = parentStateNode.instance;
        isContainer = false;
      }
    default:
      // ...省略部分代码
  }
  if (parentFiber.flags & ContentReset) {
    resetTextContent(parent);
    parentFiber.flags &= ~ContentReset;
  }
  // 获取兄弟DOM节点
  const before = getHostSibling(finishedWork);
  // 根据情况决定是调用insertBefore还是appendChild方法执行DOM操作
  if (isContainer) {
    insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
  } else {
    insertOrAppendPlacementNode(finishedWork, before, parent);
  }
}

主要功能:

  • 获取父级DOM节点
  • 获取兄弟节点
  • 根据情况决定是调用insertBefore还是appendChild方法执行DOM操作

mount时,也是这在一步把Fiber上对应的DOM树渲染到root容器中的。

其他的更新commitWork和删除commitDeletion操作都是update时会执行的了,在这里就暂不讨论。

commitLayoutEffects

// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
  // ...省略部分代码
  while (nextEffect !== null) {
    setCurrentDebugFiberInDEV(nextEffect);

    const flags = nextEffect.flags;

    if (flags & (Update | Callback)) {
      const current = nextEffect.alternate;
      // 调用生命周期componentDidMount和componentDidUpdate和hook
      commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
    }

    if (enableScopeAPI) {
      
      if (flags & Ref && nextEffect.tag !== ScopeComponent) {
        // 赋值ref
        commitAttachRef(nextEffect);
      }
    } else {
      if (flags & Ref) {
        // 赋值ref
        commitAttachRef(nextEffect);
      }
    }
		// ...省略部分代码
    nextEffect = nextEffect.nextEffect;
  }
  // ...省略部分代码
  
}

此方法主要功能如下:

  • 调用生命周期方法和hook有关的操作
  • 赋值ref

总结

本篇从ReactTop Level API的实现,再到ReactDOM.render方法的执行流程,接着到渲染阶段创建Fiber树及构建DOM树的过程,最后到commit阶段把真实DOM渲染到页面的过程,讲述了应用第一次加载的流程。

下面用一张图总结一下整个流程:

react17.01 (1).png

图太模糊,拆分为一下三张图:

react17.01.png

react17.01 (1).png

react17.01 (2).png 这样我们就系统的了解了React内部的一些架构设计和实现逻辑。过程还是比较复杂的,但是这也只是React的冰山一角。剩余的分支还有很多,比如React是如何更新状态及把状态映射到DOM上的?DOMDiff的具体实现及算法是什么?Hooks的实现及执行时机是什么?还有最重要的任务调度和异步渲染是如何实现的?都值得详细去探讨。

更多内容可以关注微信公众号:闹闹前端