React 的源码与原理解读(五):beginWork 分类处理逻辑

时间:2020-12-09 01:09:37

写在专栏开头(叠甲)

  1. 作者并不是前端技术专家,也只是一名喜欢学习新东西的前端技术小白,想要学习源码只是为了应付急转直下的前端行情和找工作的需要,这篇专栏是作者学习的过程中自己的思考和体会,也有很多参考其他教程的部分,如果存在错误或者问题,欢迎向作者指出,作者保证内容 100% 正确,请不要将本专栏作为参考答案。

  2. 本专栏的阅读需要你具有一定的 React 基础、 JavaScript 基础和前端工程化的基础,作者并不会讲解很多基础的知识点,例如:babel 是什么,jsx 的语法是什么,需要时请自行查阅相关资料。

  3. 本专栏很多部分参考了大量其他教程,若有雷同,那是作者抄袭他们的,所以本教程完全开源,你可以当成作者对各类教程进行了整合、总结并加入了自己的理解。

本一节的内容

本节的我们将从 上一节留下的问题出发,谈谈 beginWork () 中怎么样把一个 element 节点变成一个 fiber 结点,其中我们会提到我们的 beginwork 怎么样处理更新和首次渲染,通过什么样的方式来加速渲染,以及对于不同类型的节点使用怎么样不同的逻辑

beginWork

上一节中我们讲到,不论是什么类型的任务,最后都会调用 performUnitOfWork 的逻辑,其中使用了 beginWork 这个函数,它的操作很复杂,这一节我们从它开始讲起,它的代码在这个位置:

https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberBeginWork.js

它的源代码很长,我们需要一部分一部分来看,完整的源代码可以查看提供的链接,首先是它的整体架构,我们首先通过传入的 current 是不是空来判定本次渲染是首次挂载还是后续的更新,如果此处为首次渲染,didReceiveUpdate 设为 false 。注意,current !== null 这个判定将会在下面的内容中多次出现,现在可以记住它。

function beginWork(
  current: Fiber | null, 
  workInProgress: Fiber, 
  renderLanes: Lanes,
): Fiber | null {
	
  // 判定 current 是不是空来断定是更新产生还是执行之前的任务
  if (current !== null) {
    //....
  } else {
    current = false;
	//省略....
  }
  workInProgress.lanes = NoLanes;
  // 根据 workInProgress 不同的tag,进行不同的处理
  switch (workInProgress.tag) {
    //case.....
  }
  throw new Error(
    `Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
      'React. Please file an issue.',
  );
}

之后我们来看 current 不是空的情况,也就是更新逻辑,我们获取前后两次的 props ,你可以会看一下 Fiber 这节,我们有一个属性是 memoizedProps ,缓存了上一次的 props , 当时可能大家都不明白为什么需要进行缓存,其实它在这里发挥了用处,通过对比前后两次的 props 可以快速判定是不是需要更新。

  • 如果两次 props 不相等,标记为需要更新
  • 如果两个 props 相等,并且没有 挂起的更新或上下文更改(这部分和调度与优先级有关,之后会展开),我们就提前退出,复用之前的节点即可
  • 如果处于传统模式下(非分片模式,可以控制节点是不是强制更新,也就是不管变不变都更新它),判定当前节点是否要强制更新
if (current !== null) {
    // 获取新旧的 props
    const oldProps = current.memoizedProps; 
    const newProps = workInProgress.pendingProps; 

    if (
      oldProps !== newProps ||
      hasLegacyContextChanged() ||
      (__DEV__ ? workInProgress.type !== current.type : false)
    ) {
      // 如果 props 或 context 发生变化,将 Fiber 标记为需要更新
      didReceiveUpdate = true; 
    } else {
      // props和上下文都没有改变,检查是否有挂起的更新或上下文更改
      const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
        current,
        renderLanes,
      );
      if (
        !hasScheduledUpdateOrContext &&
        (workInProgress.flags & DidCapture) === NoFlags
      ) {
        // 若没有任何的更新,上下文也没发生变化,直接返回
        didReceiveUpdate = false;
        return attemptEarlyBailoutIfNoScheduledUpdate(
          current,
          workInProgress,
          renderLanes,
        );
      }
      if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
        // 传统模式下,当前节点是否要强制更新
        didReceiveUpdate = true;
      } else {
        didReceiveUpdate = false;
      }
}

当我们把 didReceiveUpdate 处理完毕后,我们根据节点的类型进行不同的更新处理,这里的逻辑很复杂,基本上每一个 tag 都需要一个单独的处理,我们讲述一些常用的内容,完整的内容可以自行阅读源码或者查阅资料:

switch (workInProgress.tag) {
    case IndeterminateComponent: {
     //****
    }
    case LazyComponent: {
     //****
    }
    case FunctionComponent: {
     //函数组件 ****
    }
    case ClassComponent: {
      //类组件 ****
    }
    case HostRoot:
      // 初始render()时,只有两棵树的根节点,current和 workInProgress 分别指向到这两棵树的根节点
      return updateHostRoot(current, workInProgress, renderLanes);
    case HostComponent:
      // html标签
      return updateHostComponent(current, workInProgress, renderLanes);
    case HostText:
      return updateHostText(current, workInProgress);
    case SuspenseComponent:
      return updateSuspenseComponent(current, workInProgress, renderLanes);
    case HostPortal:
      return updatePortalComponent(current, workInProgress, renderLanes);
    case ForwardRef: {
      //****
    }
    case Fragment:
      return updateFragment(current, workInProgress, renderLanes);
    case Mode:
      // 如 <React.StrictMode></React.StrictMode>
      return updateMode(current, workInProgress, renderLanes);
    case Profiler:
      return updateProfiler(current, workInProgress, renderLanes);
    case ContextProvider:
      return updateContextProvider(current, workInProgress, renderLanes);
    case ContextConsumer:
      return updateContextConsumer(current, workInProgress, renderLanes);
    case MemoComponent: {
      //****
    }
    case SimpleMemoComponent: {
      //****
    }
    case IncompleteClassComponent: {
      //****
    }
    case SuspenseListComponent: {
      return updateSuspenseListComponent(current, workInProgress, renderLanes);
    }
    case ScopeComponent: {
      //****
    }
    case OffscreenComponent: {
      return updateOffscreenComponent(current, workInProgress, renderLanes);
    }
    case LegacyHiddenComponent: {
      //****
    }
    case CacheComponent: {
      //****
    }
    case TracingMarkerComponent: {
      //****
    }
}

HostRoot

当节点是 HostRoot 类型的时候,也就是初始化的时候,我们传入了我们的两棵树的根节点,我们这样处理:

  • 首先我们获取传入本次的 props,之后我们获取上次的 State,你可以回看一下 Fiber 架构这一节,当时我们提到了其中有一些和 props 以及 state 相关的内容,其中 pendingProps 属性存放是本次渲染的 props ,这个会在节点生成的时候获取;而 memoizedState 属性存缓存了上一次生成的 state,这个属性是为了我们这里的算法服务的,它可以通过比较上次和这次渲染的节点是不是一样来判定我们是不是需要提前终止生成的逻辑,从而提高渲染的效率
  • 之后我们把 current 的 updateQueue 队列(其中存放了 element)复制给 workInProgress ,目的是防止再后续修改的时候相互影响(因为 js 的对象的引用传值)
  • 通过本次传递的 props 和 workInProgress 中的 element 元素,我们可以使用 processUpdateQueue 将 element 添加到 Fiber 的 memoizedState 和 updateQueue 更新队列中 ,关于这个函数,我们会在进程调度的时候展开来讲,总之在执行完成这个函数后,我们可以通过 memoizedState 拿到本次执行生成的 state,也就拿到本次的 element
  • 如果前后两次的 element 相同,那么我们可以复用之前的元素,否则我们调用 reconcileChildren 创建一个新的节点
  • 注意:如果这次是首次渲染,那么 prevChildren 肯定是 null ,所以肯定需要调用我们的 reconcileChildren 来创建我们的 Fiber
  • 最后我们返回我们的 workInProgress.child ,也就是下一个节点,如果下一个节点不是空,那么继续运行整个逻辑
//case HostRoot:  return updateHostRoot(current, workInProgress, renderLanes);

function updateHostRoot(current, workInProgress, renderLanes) {
  //把一些有用的信息推入内部栈
  pushHostRootContext(workInProgress);
  if (current === null) {
    throw new Error('Should have a current fiber. This is a bug in React.');
  }
  //获取本次的props
  const nextProps = workInProgress.pendingProps;
  //获取上传的state
  const prevState = workInProgress.memoizedState;
  //获取上次的element
  const prevChildren = prevState.element;
  // 将current节点中的updateQueue中的属性,复制给workInProgress节点
  cloneUpdateQueue(current, workInProgress);
  // 主要的任务是将 Element 添加到 Fiber 的 memoizedState 和 updateQueue 更新队列中
  processUpdateQueue(workInProgress, nextProps, null, renderLanes);
  // 得到各种更新后的最新数据
  const nextState: RootState = workInProgress.memoizedState;
  const root: FiberRoot = workInProgress.stateNode;
  pushRootTransition(workInProgress, root, renderLanes);
  //省略....
  //获得本次的 element
  const nextChildren = nextState.element;
  if (supportsHydration && prevState.isDehydrated) {
      //省略.....
  } else {
    resetHydrationState();
    // 若前后两次的 element 如果没有变化,则提前退出,直接复用之前的节点
    // 而初始时,prevChildren为null,nextChildren为将要更新的element,肯定不相等
    if (nextChildren === prevChildren) {
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    }
    // 执行 reconcileChildren 把 element 变成 fiber
    reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  }
  return workInProgress.child;
}

FunctionComponent

我们继续看 switch 语句,如果传入的节点是 FunctionComponent 也就是函数组件,我们会执行这样的逻辑:

  • 函数组件的主体就放在属性 type 中,我们获取其 type ,后续执行该 type() 即可。
  • 然后我们调用 updateFunctionComponent 函数处理我们的函数组件
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,
      );
}

之后我们来看看 updateFunctionComponent

  • 这里调用了 renderWithHooks 得到了 nextChildren
  • 然后根据我们之前得到 didReceiveUpdate 判定是不是可以提前退出复用之前的节点
  • 如果不能复用(需要更新或者首次),我们则调用 reconcileChildren
function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderLanes,
) {
  //....省略 dev 模式代码
  //获取我们的 context 信息(不知道 context 可以自行查阅 react 文档)
  let context;
  if (!disableLegacyContext) {
    const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
    context = getMaskedContext(workInProgress, unmaskedContext);
  }
  let nextChildren;
  let hasId;
  prepareToReadContext(workInProgress, renderLanes);
  // ....省略优先级调度
  // 处理 HOOKS
  nextChildren = renderWithHooks(
    current,
    workInProgress,
    Component,
    nextProps,
    context,
    renderLanes,
  );
  hasId = checkDidRenderIdHook();
  //节点更新,但是状态和props没有改变
  if (current !== null && !didReceiveUpdate) {
    bailoutHooks(current, workInProgress, renderLanes);
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }
	
  if (getIsHydrating() && hasId) {
    pushMaterializedTreeId(workInProgress);
  }
  workInProgress.flags |= PerformedWork;
  //调用更新
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

而在 renderWithHooks 这个函数中,我们根据是不是首次渲染来判定是更新我们的 hooks 还是 初始化我们的 hooks,这个我们后续会详细来说,之后我们直接执行我们的函数组件,传入它需要的 props 和 上下文信息,返回我们执行后创造出来的内部的 element 结构,这就是为什么我们的函数组件要 return 一个 jsx 的理由,它其实是在这里被作为一个函数执行,然后返回它的内部结构,我们将这个结构输入 reconcileChildren 生成我们的 Fiber

export function renderWithHooks<Props, SecondArg>(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {
  renderLanes = nextRenderLanes;
  currentlyRenderingFiber = workInProgress; 
  // 根据是否是初始化挂载,来决定是初始化hook,还是更新hook
  ReactCurrentDispatcher.current =
    current === null || current.memoizedState === null ? HooksDispatcherOnMount : HooksDispatcherOnUpdate;
  // 传入函数,执行我们的函数组件
  let children = Component(props, secondArg);

  return children;
}

ClassComponent

当我们传入是类组件的时候,我们执行 updateClassComponent() 这个函数,它的处理比函数组件复杂一些,它的大致逻辑如下:

  • 首先获取我们的上下文,然后我们会看 Fiber 中的定义, stateNode 这个属性用于记录当前 fiber 所对应的真实 dom 节点或者当前虚拟组件的实例,因为我们是 class 节点,那么这个属性就可以获取到我们的实例 ,不知道实例是什么的可以看看面向对象的概念
  • 如果实例是空的,那么我们需要通过传入的组件创建出一个实例来与 workInProgress 绑定,constructClassInstance 用于初始化节点,而 mountClassInstance 则负责挂载
  • 如果已经初始化,按照是不是首次渲染来判定我们是使用节点还是更新节点
  • 最后我们使用 finishClassComponent 结束我们的类组件操作
function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderLanes: Lanes,
) {
  
  // ... 省略 dev 模式代码
      
  let hasContext;
  if (isLegacyContextProvider(Component)) {
    hasContext = true;
    pushLegacyContextProvider(workInProgress);
  } else {
    hasContext = false;
  }
  prepareToReadContext(workInProgress, renderLanes);

  const instance = workInProgress.stateNode; // 在类组件中,stateNode存储的是类的实例
  let shouldUpdate;
  if (instance === null) {
    //没有实例,初始化一个实例
    resetSuspendedCurrentOnMountInLegacyMode(current, workInProgress);
    // 创建出一个实例,然后将 workInProgress 与这个实例绑定。
    constructClassInstance(workInProgress, Component, nextProps);
    // 挂载,执行 componentWillMount
    mountClassInstance(workInProgress, Component, nextProps, renderLanes);
    shouldUpdate = true;
  } else if (current === null) {
    // current 的 fiber 节点为空,但 workInProgress.stateNode 经有这个类组件的实例了,可以直接使用
    shouldUpdate = resumeMountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderLanes,
    );
  } else {
    // current的fiber节点不为空,判断 current 和 workInProgress 两者是否有变化
    shouldUpdate = updateClassInstance(
      current,
      workInProgress,
      Component,
      nextProps,
      renderLanes,
    );
  }

  // 执行render()方法得到element,
  const nextUnitOfWork = finishClassComponent(
    current,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderLanes,
  );
  // 省略 dev 模式代码 ....
  return nextUnitOfWork;
}

我们还是首先来看初始化的部分,constructClassInstance 函数的逻辑大致是首先初始化一个实例,如何把 state 等信息挂载在上面,最后把 workInProgress 和初始化出来的实例绑定

function constructClassInstance(workInProgress: Fiber, ctor: any, props: any): any {
  // 初始化一个
  let instance = new ctor(props, context);
  // 获取到类组件中的state,放到workInProgress中的memoizedState字段中
  const state = (workInProgress.memoizedState =
    instance.state !== null && instance.state !== undefined ? instance.state : null);
  /**
   * 将workInProgress和类的实例进行互相绑定
   * instance.updater = workInProgress;
   * workInProgress.stateNode = instance;
   */
  adoptClassInstance(workInProgress, instance);

  return instance;
}

之后我们执行 mountClassInstance 这个函数,组件中的部分生命周期在这里运行:

  • 我们首先获取上下文和 state 以及 props
  • 通过 props 和 state 我们计算出新的 state 更新给节点
  • 然后依次执行 componentWillMount 生命周期,并且设置 componentDidMount 的优先级信息,但是并不在这个函数执行
// 执行渲染之前的一些生命周期函数
function mountClassInstance(workInProgress: Fiber, ctor: any, newProps: any, renderLanes: Lanes): void {
  const instance = workInProgress.stateNode; 
  instance.props = newProps;
  instance.state = workInProgress.memoizedState;
  instance.refs = emptyRefsObject;
  // 初始化更新链表
  initializeUpdateQueue(workInProgress);

  // 获取上下文
  const contextType = ctor.contextType;
  if (typeof contextType === 'object' && contextType !== null) {
    instance.context = readContext(contextType);
  } else if (disableLegacyContext) {
    instance.context = emptyContextObject;
  } else {
    const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    instance.context = getMaskedContext(workInProgress, unmaskedContext);
  }
  instance.state = workInProgress.memoizedState;

  // 这是一个官方的 API 用于在 render 之前调用,它传入 props 和 state,返回一个对象来更新我们的 state(加入了 props 的内容)
  const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
  if (typeof getDerivedStateFromProps === 'function') {
    applyDerivedStateFromProps(workInProgress, ctor, getDerivedStateFromProps, newProps);
    instance.state = workInProgress.memoizedState;
  }

  if (
    typeof ctor.getDerivedStateFromProps !== 'function' &&
    typeof instance.getSnapshotBeforeUpdate !== 'function' &&
    (typeof instance.UNSAFE_componentWillMount === 'function' || typeof instance.componentWillMount === 'function')
  ) {
    // 执行 componentWillMount 和 UNSAFE_componentWillMount 
    callComponentWillMount(workInProgress, instance);
    // 如果是更新节点,执行更新操作,这个后续会讲到
    processUpdateQueue(workInProgress, newProps, instance, renderLanes);
    instance.state = workInProgress.memoizedState; 
  }

  //设置 componentDidMount 的优先级信息(并不在此时运行)
  if (typeof instance.componentDidMount === 'function') {
    let fiberFlags: Flags = Update;
    if (enableSuspenseLayoutEffectSemantics) {
      fiberFlags |= LayoutStatic;
    }
    workInProgress.flags |= fiberFlags;
  }
}

最后我们来看看 finishClassComponent ,它的逻辑就很简单了,它调用了类组件的 render 方法获取了对应的 element 元素,之后还是调用了 reconcileChildren 方法

function finishClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  shouldUpdate: boolean,
  hasContext: boolean,
  renderLanes: Lanes,
) {
  const instance = workInProgress.stateNode;
  // 使用 render() 方法获取 jsx 对应的 element 结构
  nextChildren = instance.render();

  // 调用函数 reconcileChildren() 
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  workInProgress.memoizedState = instance.state;
  return workInProgress.child;
}

HostComponent 和 HostText

这个类型对应的是原生的 HTML ,它调用了 updateHostComponent 这个处理方法,它仅仅是多了一个逻辑判定是不是文本节点,这个逻辑在 shouldSetTextContent 中:

function updateHostComponent(current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes) {
  pushHostContext(workInProgress);
  //首次渲染进行 hydrate 的操作,也就是复用原本存在的root的内部的DOM节点。后续提到 hydrate
  if (current === null) {
    tryToClaimNextHydratableInstance(workInProgress);
  }

  const type = workInProgress.type; 
  const nextProps = workInProgress.pendingProps; 
  const prevProps = current !== null ? current.memoizedProps : null;
  let nextChildren = nextProps.children;

  // 判断该节点是否是文本节点, 即里面不再嵌套其他类型的节点
  const isDirectTextChild = shouldSetTextContent(type, nextProps);

  if (isDirectTextChild) {
    // 文本节点的话,没有子节点
    nextChildren = null;
  } 
  // 如果是更新一个节点,它之前是文本节点,但是现在不是文本节点
  else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
    // 设置 ContentReset,表示文本节点重置
    workInProgress.flags |= ContentReset;
  }
  // 获取 Component 的 DOM 实例,更新 ref
  markRef(current, workInProgress);

  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}
//判断是不是文本节点的逻辑
function shouldSetTextContent(type: string, props: Props): boolean {
  return (
    type === 'textarea' ||
    type === 'option' ||
    type === 'noscript' ||
    typeof props.children === 'string' ||
    typeof props.children === 'number' ||
    (typeof props.dangerouslySetInnerHTML === 'object' &&
      props.dangerouslySetInnerHTML !== null &&
      props.dangerouslySetInnerHTML.__html != null)
  );
}

updateHostText 用于处理文本,对应的类别是 HostText ,它的处理类似上面文本节点的处理:先调用 tryToClaimNextHydratableInstance 然后直接返回 null (因为不可能有孩子了)

function updateHostText(current, workInProgress) {
  if (current === null) {
    tryToClaimNextHydratableInstance(workInProgress);
  }
  return null;
}

IndeterminateComponent

这个有一个例外,就是我们的 FunctionComponent,在第一次识别的时候会被认为是 IndeterminateComponent ,因为可能有以下几种特殊的函数组件,他们是函数组件,但是写法又可能近似于类组件,所以我们需要特殊的处理来判别他们的类型:

// function中 return 带有 render() 的obj
function App() {
  return {
    render() {
      return <p>function render</p>;
    },
  };
}

// render() 在 函数 App() 的prototype上
function App() {}
App.prototype.render = () => {
  return <p>function prototype render</p>;
};

// 继承React.Component
function App() {
  return {
    componentDidMount() {
      console.log('componentDidMount');
    },
    render() {
      return <p>function render</p>;
    },
  };
}
App.prototype = React.Component.prototype; // 或 new React.Component()

// function 中直接return一个jsx
function App() {
  return <p>function jsx</p>;
}

这个处理在 mountIndeterminateComponent 中进行,它通过判定返回的 value 的类型

function mountIndeterminateComponent(
  _current, // 好奇怪,这里为什么要用下划线开头
  workInProgress,
  Component,
  renderLanes,
) {
  // 省略....
  let value = renderWithHooks(null, workInProgress, Component, props, context, renderLanes);

  if (
    !disableModulePatternComponents &&
    typeof value === 'object' &&
    value !== null &&
    typeof value.render === 'function' &&
    value.$$typeof === undefined
  ) {
    // 类组件
    workInProgress.tag = ClassComponent;
  } else {
    // 函数组件
    workInProgress.tag = FunctionComponent;
  }
}

总结

这一节中,我们通过解析 beginWork 函数,明白了我们是怎么样把一个 element 节点变成一个 fiber 结点的:

  • 它首先通过比较两次的 props 判定本次任务是更新还是首次渲染,需不需要任务是不是需要更新,还是可以复用之前的节点
  • 之后根据我们传入节点的类型分别处理他们:
  • 如果你传入一个 HostRoot , 那么它通过比较两次传入的 element 判定是不是需要更新,需要更新则传入 reconcileChildren
  • 如果你传入一个 FunctionComponent,那么我们先处理它的 hooks,然后传入 props 和上下文来调用这个函数,返回 element 元素传入 reconcileChildren
  • 如果你传入一个 ClassComponent ,那么我们需要先初始化一个这个 class 的实例挂载到 fiber 上,然后处理它的 props 生成 state,再处理它的 componentWillMount 生命周期 ,最后调用 render 方法获取它的 element ,传入 reconcileChildren
  • 如果你传入一个原生的 HTML 节点,它会判定它是不是文本节点,如果是则停止后续的处理,否则传入 reconcileChildren;如果你传入的是文本节点,那么直接停止后续的处理
  • 有一种特殊情况是,因为 React 中的函数组件有多种写法,有些形似函数组件的也可能是类组件,为了保险起见,函数组件会先被设定为 IndeterminateComponent,然后在对这个类型的处理中,会识别你写的组件是函数组件还是类组件

在这一节中,我们只是挑选了一些覆盖大部分情况的 tag 来讲解,如果你想了解其他特殊的 tag 的处理可以自行阅读源码或者查看其他大神的教程。在经过了这一节后,我们的整个渲染流程只剩下了最后一步,那就是 reconcileChildren 这个函数,调用它也预示着我们进入了 React 渲染流程的 reconcile 阶段,下一节我们将讲解它,而 reconcile 中我们会遇到 React 最经典的 DIFF 算法,这也是 React 的精髓所在,它帮助虚拟 DOM 高效的渲染更新,欢迎大家持续关注专栏。

参考

https://www.xiabingbao.com/post/react/react-beginwork-riew9h.html

https://zhuanlan.zhihu.com/p/115533068