前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React源码解析之IndeterminateComponent

React源码解析之IndeterminateComponent

作者头像
进击的小进进
发布2020-02-14 11:03:39
8800
发布2020-02-14 11:03:39
举报

前言:

React源码解析之FunctionComponent(中) 提到了createFiberFromElement

代码语言:javascript
复制
const created = createFiberFromElement(
        element,
        returnFiber.mode,
        expirationTime,
      );

createFiberFromElement里面又调用了createFiberFromTypeAndProps

代码语言:javascript
复制
//通过type和 props 来创建 fiber
export function createFiberFromTypeAndProps(
  type: any, // React$ElementType
  key: null | string,
  pendingProps: any,
  owner: null | Fiber,
  mode: TypeOfMode,
  expirationTime: ExpirationTime,
): Fiber {
  let fiber;
  //附一个默认值
  //只有 FunctionComponent 没有再次设 fiberTag,所以它的默认 tag 就是 IndeterminateComponent
  let fiberTag = IndeterminateComponent;
  // The resolved type is set if we know what the final type will be. I.e. it's not lazy.
  let resolvedType = type;
  if (typeof type === 'function') {
    if (shouldConstruct(type)) {
      fiberTag = ClassComponent;
      if (__DEV__) {
        resolvedType = resolveClassForHotReloading(resolvedType);
      }
    } else {
      if (__DEV__) {
        resolvedType = resolveFunctionForHotReloading(resolvedType);
      }
    }
  } else if (typeof type === 'string') {
    //<div></div>
    fiberTag = HostComponent;
  } else {
    //创建 react 的内置组件
    getTag: switch (type) {
      case REACT_FRAGMENT_TYPE:
        return createFiberFromFragment(
          pendingProps.children,
          mode,
          expirationTime,
          key,
        );
      case REACT_CONCURRENT_MODE_TYPE:
        fiberTag = Mode;
        mode |= ConcurrentMode | BatchedMode | StrictMode;
        break;
      case REACT_STRICT_MODE_TYPE:
        fiberTag = Mode;
        mode |= StrictMode;
        break;
      case REACT_PROFILER_TYPE:
        return createFiberFromProfiler(pendingProps, mode, expirationTime, key);
      case REACT_SUSPENSE_TYPE:
        return createFiberFromSuspense(pendingProps, mode, expirationTime, key);
      case REACT_SUSPENSE_LIST_TYPE:
        return createFiberFromSuspenseList(
          pendingProps,
          mode,
          expirationTime,
          key,
        );
      default: {
        //React.createRef,React.createContext返回的均为 object
        //根据不同的类型,赋 fibertag
        if (typeof type === 'object' && type !== null) {
          switch (type.$$typeof) {
            case REACT_PROVIDER_TYPE:
              fiberTag = ContextProvider;
              break getTag;
            case REACT_CONTEXT_TYPE:
              // This is a consumer
              fiberTag = ContextConsumer;
              break getTag;
            case REACT_FORWARD_REF_TYPE:
              fiberTag = ForwardRef;
              if (__DEV__) {
                resolvedType = resolveForwardRefForHotReloading(resolvedType);
              }
              break getTag;
            case REACT_MEMO_TYPE:
              fiberTag = MemoComponent;
              break getTag;
            case REACT_LAZY_TYPE:
              fiberTag = LazyComponent;
              resolvedType = null;
              break getTag;
            case REACT_EVENT_COMPONENT_TYPE:
              if (enableFlareAPI) {
                return createFiberFromEventComponent(
                  type,
                  pendingProps,
                  mode,
                  expirationTime,
                  key,
                );
              }
              break;
          }
        }
        let info = '';
        //删除了 dev 代码
        //如果走到这里,说明 type 不是 React 内置的类型,报警告
        invariant(
          false,
          'Element type is invalid: expected a string (for built-in ' +
            'components) or a class/function (for composite components) ' +
            'but got: %s.%s',
          type == null ? type : typeof type,
          info,
        );
      }
    }
  }
  //创建 fiber 对象
  fiber = createFiber(fiberTag, pendingProps, key, mode);
  fiber.elementType = type;
  fiber.type = resolvedType;
  fiber.expirationTime = expirationTime;

  return fiber;
}

我们重点注意一下fiberTag

代码语言:javascript
复制
//只有 FunctionComponent 没有再次设 fiberTag,所以它的默认 tag 就是 IndeterminateComponent
  let fiberTag = IndeterminateComponent;

从这段代码可以看到,fiberTag的默认值是IndeterminateComponent, 随后有赋值成ClassComponent/HostComponent/Mode/ContextProvider/ContextConsumer/ForwardRef/MemoComponent/LazyComponent等, 但唯独没有FunctionComponent的 tag 类型, 那么 React源码解析之FunctionComponent(上) 中怎么会有fiberTag = FunctionComponent的更新呢?

答案就在mountIndeterminateComponent中:

代码语言:javascript
复制
//如果节点是有更新的
  //根据节点类型进行组件的更新
  switch (workInProgress.tag) {
    case IndeterminateComponent: {
      return mountIndeterminateComponent(
        current,
        workInProgress,
        workInProgress.type,
        renderExpirationTime,
      );
    }
    xxx...
    xxx...
}

也就是说, FunctionComponent一开始的状态是IndeterminateComponent,经过mountIndeterminateComponent()后,才得到了FunctionComponent的fiberTag

接下来我们就看下mountIndeterminateComponent

一、mountIndeterminateComponent

作用: 进一步明确FunctionComponent以何种方式更新

源码:

代码语言:javascript
复制
//进一步明确 FunctionComponent 以何种方式更新
function mountIndeterminateComponent(
  _current,
  workInProgress,
  Component,
  renderExpirationTime,
) {
  //只有在第一次渲染的时候,才会调用mountIndeterminateComponent(),此时_current应该为 null
  //出现_current不为 null 的情况,一般是第一次渲染的时候捕获到 error 了,此时就需要重置_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;
  //=========context 可跳过===========================================================
  const unmaskedContext = getUnmaskedContext(workInProgress, Component, false);
  const context = getMaskedContext(workInProgress, unmaskedContext);

  prepareToReadContext(workInProgress, renderExpirationTime);
  prepareToReadEventComponents(workInProgress);
  //=======================================================================

  let value;

  if (__DEV__) {
    //删除了 dev 代码
  } else {
    //因为FunctionComponent一开始是处于indeterminateComponent的状态下的,所以会涉及到 hooks
    //渲染的过程中,对里面用到的 hook函数做一些操作

    //renderWithHooks的解析请看 React源码解析之FunctionComponent(上):
    //https://juejin.im/post/5ddbe114e51d45231e010c75
    value = renderWithHooks(
      null,
      workInProgress,
      Component,
      props,
      context,
      renderExpirationTime,
    );
  }
  // React DevTools reads this flag.
  workInProgress.effectTag |= PerformedWork;

  //确认是否是 ClassComponent,因为只有ClassComponent有 render() 方法

  //关键是这个判断条件
  //如果这个条件成立的话,就表明可以在 FunctionComponent 中使用 ClassComponent 的 API !!
  if (
    typeof value === 'object' &&
    value !== null &&
    typeof value.render === 'function' &&
    value.$$typeof === undefined
  ) {
    //删除了 dev 代码

    // Proceed under the assumption that this is a class instance
    workInProgress.tag = ClassComponent;

    // Throw out any hooks that were used.
    // 重置 hooks 状态,也就是不使用 hooks
    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.

    //下面的这些 function 在 updateClassComponent() 中都有解析过,就不再赘述了
    //https://juejin.im/post/5e1bc74ee51d45020837e8f4
    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,
    );
  }
  //否则就是 FunctionComponent
  else {
    // Proceed under the assumption that this is a function component
    //正式赋予 tag 为 FunctionComponent,将按照FunctionComponent的流程更新组件
    workInProgress.tag = FunctionComponent;
    //删除了 dev 代码
    //reconcileChildren的解析请看:React源码解析之FunctionComponent(上)
    //https://juejin.im/post/5ddbe114e51d45231e010c75
    reconcileChildren(null, workInProgress, value, renderExpirationTime);
    if (__DEV__) {
      validateFunctionComponentInDev(workInProgress, Component);
    }
    return workInProgress.child;
  }
}

解析: (1) mountIndeterminateComponent()是在组件第一次渲染的时候调用的, 此时_current应为null 只有当组件被挂起(suspended)的时候,_current才不为null, 而suspended的情况一般是捕获到了error,所以此时就需要重置_currentworkInProgress部分属性

(2) 关于renderWithHooks()的解析请看: React源码解析之FunctionComponent(上)

(3) resetHooks()的作用是重置 hooks 状态,源码比较简单,就不解析了:

代码语言:javascript
复制
// 重置 hooks 状态
export function resetHooks(): void {
  // We can assume the previous dispatcher is always this one, since we set it
  // at the beginning of the render phase and there's no re-entrancy.
  ReactCurrentDispatcher.current = ContextOnlyDispatcher;

  // This is used to reset the state of this module when a component throws.
  // It's also called inside mountIndeterminateComponent if we determine the
  // component is a module-style component.
  renderExpirationTime = NoWork;
  currentlyRenderingFiber = null;

  currentHook = null;
  nextCurrentHook = null;
  firstWorkInProgressHook = null;
  workInProgressHook = null;
  nextWorkInProgressHook = null;

  //删除了 dev 代码

  remainingExpirationTime = NoWork;
  componentUpdateQueue = null;
  sideEffectTag = 0;

  didScheduleRenderPhaseUpdate = false;
  renderPhaseUpdates = null;
  numberOfReRenders = 0;
}

(4) 重点看下这个判断条件:

代码语言:javascript
复制
  if (
    typeof value === 'object' &&
    value !== null &&
    typeof value.render === 'function' &&
    value.$$typeof === undefined
  ){

}

value在开发角度上讲,应该是指FunctionComponentreturn返回的值, 如果value中包含render()方法的话,则按ClassComponent的方式更新组件, 否则就确认为真正的FunctionComponenttag赋值为FunctionComponent

(5) 当 (4) 判断为true时,里面执行的一些function的解析请看: React源码解析之updateClassComponent(上)

(6) 当 (4) 判断为false时,里面执行的一些function的解析请看: React源码解析之FunctionComponent(上)

按照 (4) 的情况,是不是在FunctionComponent中写render()方法后,就能调用ClassComponent的 API 呢? 我们来试下:

代码语言:javascript
复制
import React from "react";
import { Dispatch } from "redux";

interface TestProps {
  dispatch: Dispatch<any>;
}

const Test: React.FC<TestProps> = props => {
  const { dispatch, } = props;

  return {
    componentWillMount(){
      console.log('在FunctionComponent中调用了componentWillMount')
    },

    render() {
      return (
        <div>
          在 FunctionComponent 中调用 ClassComponent 的 API
        </div>
      )
    }
  }
};

效果图:

可以看到,在FunctionComponent中是可以调用ClassComponent的API的

当然,React 也报了warning,不建议开发者使用这种 hack 手段去编写代码。

GitHub:

https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberBeginWork.js


小进进还没开通留言功能,觉得不错的话,点「在看」、转发朋友圈都是一种支持 (●'◡'●)ノ 欢迎关注本公众号:

(完)

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 webchen 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言:
  • 一、mountIndeterminateComponent
  • GitHub:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档