React16源码: React中的IndeterminateComponent的源码实现

IndeterminateComponent

1 )概述

  • 这是一个比较特殊的component的类型, 就是还没有被指定类型的component
  • 在一个fibrer被创建的时候,它的tag可能会是 IndeterminateComponent
  • 在 packages/react-reconciler/src/ReactFiber.js 中,有一个方法
  • createFiberFromTypeAndProps 中,一开始就声明了
    let fiberTag = IndeterminateComponent;
    let resolvedType = type;
    // 一开始
    if (typeof type === 'function') {
      // 在 function 下只是判断了 constructor是否存在
      // 在不存在的时候,是否认为它是一个 FunctionComponent 呢,实际上并没有
      // 实际上我们写的任何一个 function component 一开始就是 IndeterminateComponent 类型
      if (shouldConstruct(type)) {
        // 存在,则赋值为 ClassComponent
        fiberTag = ClassComponent;
      }
    } else if (typeof type === 'string') {
      fiberTag = HostComponent;
    } else {
      // 省略
    }
    
  • 在最终调用 createFiber 创建 Fiber 对象

2 )源码

定位到 packages/react-reconciler/src/ReactFiber.js
进入 mountIndeterminateComponent 方法

// 
function mountIndeterminateComponent(
  _current,
  workInProgress,
  Component,
  renderExpirationTime,
) {
  // 首先判断 _current 是否存在,存在则进行初始化操作
  // 因为只有在第一次渲染的时候,才有 indeterminate component 这种情况
  // 经过第一次渲染之后,我们就会发现 indeterminate component 的具体的类型
  // 这种情况,可能是中途抛出一个 error 或 promise 等情况,比如 Suspense 组件
  // 这时候初始化是为了 去除 _current 和 workInProgress 相关联系,因为需要重新进行初次渲染的流程
  if (_current !== null) {
    // An indeterminate component only mounts if it suspended inside a non-
    // concurrent tree, in an inconsistent state. We want to treat it like
    // a new mount, even though an empty version of it already committed.
    // Disconnect the alternate pointers.
    _current.alternate = null;
    workInProgress.alternate = null;
    // Since this is conceptually a new fiber, schedule a Placement effect
    workInProgress.effectTag |= Placement;
  }

  const props = workInProgress.pendingProps;
  const unmaskedContext = getUnmaskedContext(workInProgress, Component, false);
  const context = getMaskedContext(workInProgress, unmaskedContext);

  prepareToReadContext(workInProgress, renderExpirationTime);
  prepareToUseHooks(null, workInProgress, renderExpirationTime);

  let value;

  if (__DEV__) {
    if (
      Component.prototype &&
      typeof Component.prototype.render === 'function'
    ) {
      const componentName = getComponentName(Component) || 'Unknown';

      if (!didWarnAboutBadClass[componentName]) {
        warningWithoutStack(
          false,
          "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 & StrictMode) {
      ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null);
    }

    ReactCurrentOwner.current = workInProgress;
    value = Component(props, context); 
  } else {
    value = Component(props, context); // 调用 Component 方法
  }
  // React DevTools reads this flag.
  workInProgress.effectTag |= PerformedWork;

  // 符合这个条件,认为是 ClassComponent, 具有 render 方法
  if (
    typeof value === 'object' &&
    value !== null &&
    typeof value.render === 'function' &&
    value.$$typeof === undefined
  ) {
    // Proceed under the assumption that this is a class instance
    workInProgress.tag = ClassComponent; // 这里认为它是一个 ClassComponent

    // Throw out any hooks that were used.
    resetHooks();

    // 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;

    const getDerivedStateFromProps = Component.getDerivedStateFromProps;
    if (typeof getDerivedStateFromProps === 'function') {
      applyDerivedStateFromProps(
        workInProgress,
        Component,
        getDerivedStateFromProps,
        props,
      );
    }

    adoptClassInstance(workInProgress, value);
    mountClassInstance(workInProgress, Component, props, renderExpirationTime);
    return finishClassComponent(
      null,
      workInProgress,
      Component,
      true,
      hasContext,
      renderExpirationTime,
    );
  } else {
    // 否则按照 FunctionComponent 来渲染
    // Proceed under the assumption that this is a function component
    workInProgress.tag = FunctionComponent; 
    value = finishHooks(Component, props, value, context);
    if (__DEV__) {
      if (Component) {
        warningWithoutStack(
          !Component.childContextTypes,
          '%s(...): childContextTypes cannot be defined on a function component.',
          Component.displayName || Component.name || 'Component',
        );
      }
      if (workInProgress.ref !== null) {
        let info = '';
        const ownerName = ReactCurrentFiber.getCurrentFiberOwnerNameInDevOrNull();
        if (ownerName) {
          info += '

Check the render method of `' + ownerName + '`.';
        }

        let warningKey = ownerName || workInProgress._debugID || '';
        const debugSource = workInProgress._debugSource;
        if (debugSource) {
          warningKey = debugSource.fileName + ':' + debugSource.lineNumber;
        }
        if (!didWarnAboutFunctionRefs[warningKey]) {
          didWarnAboutFunctionRefs[warningKey] = true;
          warning(
            false,
            'Function components cannot be given refs. ' +
              'Attempts to access this ref will fail.%s',
            info,
          );
        }
      }

      if (typeof Component.getDerivedStateFromProps === 'function') {
        const componentName = getComponentName(Component) || 'Unknown';

        if (!didWarnAboutGetDerivedStateOnFunctionComponent[componentName]) {
          warningWithoutStack(
            false,
            '%s: Function components do not support getDerivedStateFromProps.',
            componentName,
          );
          didWarnAboutGetDerivedStateOnFunctionComponent[componentName] = true;
        }
      }

      if (
        typeof Component.contextType === 'object' &&
        Component.contextType !== null
      ) {
        const componentName = getComponentName(Component) || 'Unknown';

        if (!didWarnAboutContextTypeOnFunctionComponent[componentName]) {
          warningWithoutStack(
            false,
            '%s: Function components do not support contextType.',
            componentName,
          );
          didWarnAboutContextTypeOnFunctionComponent[componentName] = true;
        }
      }
    }
    reconcileChildren(null, workInProgress, value, renderExpirationTime);
    return workInProgress.child;
  }
}
  • 基于上述判断条件 能认定是一个 ClassComponent,后续渲染一定会按照 ClassComponent 进行
    • if ( typeof value === 'object' && value !== null && typeof value.render === 'function' && value.$$typeof === undefined ){}
  • 现在来测试一下,看下 function component 是否可以执行
    import React from 'react'
    
    export default function TestIndeterminationComponent() {
      return {
        componentDidMount() {
          console.log('invoker')
        },
        render() {
          return <span>aaa</span>
        }
      }
    }
    
    • 上述都能正常显示,以及打印 console 输出
    • 也就是说,对于一个 function component, 如果里面 return 的对象,具有 render 方法
    • 就认为它是一个 class component 一样的类型,去使用它
    • 并且在里面声明的生命周期方法都会被它调用
    • 这是 IndeterminateComponent 的特性
  • 在最初我们渲染的时候,所有的 function component 都是 IndeterminateComponent 的类型
  • 在第一次渲染之后,我们根据渲染类型的返回,最终得到具体类型
  • 可以通过 function component 返回一个对象的方式去渲染一个类似 classComponent 这样的类型
  • 注意,一般不推荐这么写