Skip to content

IndeterminateComponent 组件

不确定类型的组件类型

在从 HostRoot 节点向下遍历处理子孙节点的时候,在 beginWork 阶段都会根据 React.jsx('div' , {} ,[] ) 返回的结果去创建对应节点的 FiberNode 对象,具体在 createFiberFromTypeAndProps()函数中,因为不知道传入的函数具体是一个 函数式组件存函数类组件转换后的函数,所以对于 typeof type === 'function' 的情况,第一次会将其作为一个 未知类型组件(IndeterminateComponent) 去处理。

js
export function createFiberFromTypeAndProps(
  type: any, // React$ElementType
  key: null | string,
  pendingProps: any,
  owner: null | Fiber,
  mode: TypeOfMode,
  lanes: Lanes
): Fiber {
  // 节点默认类型就是 未知类型组件
  let fiberTag = n;
  // The resolved type is set if we know what the final type will be. I.e. it's not lazy.
  let resolvedType = type;
  // type 如果是函数类型 那么可能是以下类型
  // 1. 函数组件类型
  // 2. 存函数类型
  // 3. Class 组件类型
  // 4. Class组件编译成函数 类型
  if (typeof type === "function") {
    // 判断是否是 类组件
    // 如果 Class.prototype.isReactComponent 那么说明这个肯定是类组件类型的
    if (shouldConstruct(type)) {
      fiberTag = ClassComponent;
    }
  }
}

从上述代码可以看出,对于创建的组件类型,其主要根据以下条件进行判断

  1. typeof type === 'function' 类型

传入的就是一个函数类型, 如 函数式组件

  1. 字符串类型的组件

对于字符串类型的组件,其一般都是元素类型, 如 <div></div> 这种, 所以对于这种类型的组件,其在编译过程中会将其编译成一个 React.jsx() 函数, 如 <div></div> => { type : 'div', key: null, ref: null, props: { children: null } }

所以这时候我们可以通过 type 去判断是否是字符串类型的组件, 如 typeof type === 'string'

  1. 内置的组件类型

对于内置的组件类型,其在编译过程中会将其编译成一个 React.jsx() 函数, 如 <></> => { type : React.Fragment , key: null, ref: null, props: { children: null } }

所以这时候我们可以通过 type 去判断是否是内置组件类型, 如 type === REACT_FRAGMENT_TYPE

  1. type 是对象类型,且存在 type.$$typeof 类型

这种类型主要是以下几种

  • Context.ProviderContext.Consumer 组件 这种可以看 Context 组件 这一届

  • React.xx()处理的组件

    • React.memo() 处理的组件
    • React.lazy() 处理的组件
    • React.forwardRef() 处理的组件

其他的我们都作为 未知类型组件 去处理,其中比较特殊的就是 函数类型的组件, 因为函数类型的组件可能是 函数式组件存函数类组件类组件编译成的函数 ,在第一次编译的时候无法知道具体的类型,所以需要在后面的 updateWork 阶段去判断具体的类型。

但是实际上其将 Class 类型且存在 constructor 的优先判断作为 ClassComponent 类型,所以这个大概率是 FunctionComponent 类型。

所以在处理的时候,也是优先按照 FunctionComponent 类型去处理。

源码

js
/**
 * 所有的函数式组件默认第一次都是按照 IndeterminateComponent 类型来处理的
 *   在这个类型中会按照函数是组件的方式去执行函数,且根据执行后的返回值判断这个函数究竟是什么类型的组件
 *    1. Class 组件经过编译转换后的 函数
 *    2. 存函数
 *    3. 函数式组件
 *    4. module 模式的组件  return { render() {  return <div></div> }}
 *    5. 内置的组件类型 如 Provider 等
 *
 *   对于上述几种类型的函数会根据其返回值 value的类型 判断具体类型
 *    - 存在 value.render 且不存在 $$typeof  =>  Class 类型的组件
 *    - 其他的将其类型修改成 FunctionComponent
 *
 */
function mountIndeterminateComponent(
  _current,
  workInProgress,
  Component,
  renderLanes
) {
  resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);

  // start:   开始处理  context
  const props = workInProgress.pendingProps;
  let context;
  if (!disableLegacyContext) {
    const unmaskedContext = getUnmaskedContext(
      workInProgress,
      Component,
      false
    );
    context = getMaskedContext(workInProgress, unmaskedContext);
  }

  prepareToReadContext(workInProgress, renderLanes);
  // end:   处理  context
  let value;
  let hasId;

  if (enableSchedulingProfiler) {
    markComponentRenderStarted(workInProgress);
  }
  if (__DEV__) {
    if (
      Component.prototype &&
      typeof Component.prototype.render === "function"
    ) {
      const componentName = getComponentNameFromType(Component) || "Unknown";

      if (!didWarnAboutBadClass[componentName]) {
        console.error(
          "The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
            "This is likely to cause errors. Change %s to extend React.Component instead.",
          componentName,
          componentName
        );
        didWarnAboutBadClass[componentName] = true;
      }
    }

    if (workInProgress.mode & StrictLegacyMode) {
      ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null);
    }

    setIsRendering(true);
    ReactCurrentOwner.current = workInProgress;
    value = renderWithHooks(
      null,
      workInProgress,
      Component,
      props,
      context,
      renderLanes
    );
    hasId = checkDidRenderIdHook();
    setIsRendering(false);
  } else {
    // 直接按照函数组件的方式处理
    value = renderWithHooks(
      null,
      workInProgress,
      Component,
      props,
      context,
      renderLanes
    );
    hasId = checkDidRenderIdHook();
  }
  if (enableSchedulingProfiler) {
    markComponentRenderStopped();
  }

  // React DevTools reads this flag.
  workInProgress.flags |= PerformedWork;

  if (__DEV__) {
    // Support for module components is deprecated and is removed behind a flag.
    // Whether or not it would crash later, we want to show a good message in DEV first.
    if (
      typeof value === "object" &&
      value !== null &&
      typeof value.render === "function" &&
      value.$$typeof === undefined
    ) {
      const componentName = getComponentNameFromType(Component) || "Unknown";
      if (!didWarnAboutModulePatternComponent[componentName]) {
        console.error(
          "The <%s /> component appears to be a function component that returns a class instance. " +
            "Change %s to a class that extends React.Component instead. " +
            "If you can't use a class try assigning the prototype on the function as a workaround. " +
            "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
            "cannot be called with `new` by React.",
          componentName,
          componentName,
          componentName
        );
        didWarnAboutModulePatternComponent[componentName] = true;
      }
    }
  }

  // 根据函数的返回值来判断 函数是什么类型的
  // 1. 函数的实例对象返回值 判断是否是 类组件 还是 函数组件
  if (
    // Run these checks in production only if the flag is off.
    // Eventually we'll delete this branch altogether.
    // 是否禁用  module 模式的组件  return { render() {  return <div></div> }}
    !disableModulePatternComponents &&
    typeof value === "object" &&
    value !== null &&
    // 存在 render 函数
    typeof value.render === "function" &&
    // 不是内置的组件类型
    value.$$typeof === undefined
  ) {
    if (__DEV__) {
      const componentName = getComponentNameFromType(Component) || "Unknown";
      if (!didWarnAboutModulePatternComponent[componentName]) {
        console.error(
          "The <%s /> component appears to be a function component that returns a class instance. " +
            "Change %s to a class that extends React.Component instead. " +
            "If you can't use a class try assigning the prototype on the function as a workaround. " +
            "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
            "cannot be called with `new` by React.",
          componentName,
          componentName,
          componentName
        );
        didWarnAboutModulePatternComponent[componentName] = true;
      }
    }

    // Proceed under the assumption that this is a class instance
    // 组件的类型是 类组件
    workInProgress.tag = ClassComponent;

    // Throw out any hooks that were used.
    workInProgress.memoizedState = null;
    workInProgress.updateQueue = null;

    // Push context providers early to prevent context stack mismatches.
    // During mounting we don't know the child context yet as the instance doesn't exist.
    // We will invalidate the child context in finishClassComponent() right after rendering.
    let hasContext = false;
    if (isLegacyContextProvider(Component)) {
      hasContext = true;
      pushLegacyContextProvider(workInProgress);
    } else {
      hasContext = false;
    }

    workInProgress.memoizedState =
      value.state !== null && value.state !== undefined ? value.state : null;

    initializeUpdateQueue(workInProgress);

    adoptClassInstance(workInProgress, value);
    mountClassInstance(workInProgress, Component, props, renderLanes);
    return finishClassComponent(
      null,
      workInProgress,
      Component,
      true,
      hasContext,
      renderLanes
    );
  } else {
    // Proceed under the assumption that this is a function component
    // 修改组件的类型为 函数组件
    workInProgress.tag = FunctionComponent;
    if (__DEV__) {
      if (disableLegacyContext && Component.contextTypes) {
        console.error(
          "%s uses the legacy contextTypes API which is no longer supported. " +
            "Use React.createContext() with React.useContext() instead.",
          getComponentNameFromType(Component) || "Unknown"
        );
      }

      if (
        debugRenderPhaseSideEffectsForStrictMode &&
        workInProgress.mode & StrictLegacyMode
      ) {
        setIsStrictModeForDevtools(true);
        try {
          value = renderWithHooks(
            null,
            workInProgress,
            Component,
            props,
            context,
            renderLanes
          );
          hasId = checkDidRenderIdHook();
        } finally {
          setIsStrictModeForDevtools(false);
        }
      }
    }

    if (getIsHydrating() && hasId) {
      pushMaterializedTreeId(workInProgress);
    }
    // 调和子节点
    reconcileChildren(null, workInProgress, value, renderLanes);
    if (__DEV__) {
      validateFunctionComponentInDev(workInProgress, Component);
    }
    return workInProgress.child;
  }
}

分析

对于未知类型的,其还是按照通用的一套流程进行处理的,即 1. 处理需要的上下文 context 对象 2. 进行统计性能

然后优先按照 FunctionComponent 处理,所以需要切换 hooks 的上下文去执行函数,最终根据函数的结果来判断具体的类型 1. module 模式的组件 return { render() { return <div></div> }} 2. 是否存在 render 实例对象 , 存在说明这是 Class 类型的组件 3. 是否是内置组件 value.$$typeof === undefined

去判断是否是 Class 类型的组件,如果是 Class 类型的组件又会按照 ClassComponent 的流程执行一遍,并将 workInProgress.tag = ClassComponent;,这样更新的时候就不需要按照未知类型处理了

如果不是,那就是函数式组件了 ,修改组件的类型为函数式组件 workInProgress.tag = FunctionComponent;

结论

  1. 对于未知类型的组件主要是处理以下几种情况 经过Babel编译的Class类型的组件module模式的组件 和普通的函数组件,对于前面两种类型的函数,只有执行完成后才知道组件具体是否是 ClassComponnet
  2. 对于上述两种特殊类型的组件需要尽量避免,如 Class 类型的组件尽量使用原生的 Class 方法定义,不然初次渲染的时候组件会执行两次从而造成性能浪费