最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • React应用mount时全流程解析

    正文概述 掘金(闹闹前端)   2021-03-19   628

    引言

    前端框架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)的关系如图:

    React应用mount时全流程解析

    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树结构:

    React应用mount时全流程解析

    好,现在对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渲染到页面的过程,讲述了应用第一次加载的流程。

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

    React应用mount时全流程解析

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

    React应用mount时全流程解析

    React应用mount时全流程解析

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

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


    起源地下载网 » React应用mount时全流程解析

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元