React源码解析之updateClassComponent(下)

前言:

在上篇 React源码解析之updateClassComponent(上) 中,我们讨论了更新 ClassComponent 的第一种情况— —「类实例(class instance)未被创建」的情况。

本文将先讨论第二种情况:

类实例存在,但 current 为 null,即第一次渲染的情况:

//第一次渲染
  else if (current === null) {
    // In a resume, we'll already have an instance we can reuse.
    //此时 instance 已经创建,复用 class 实例,更新 props/state,
    // 调用生命周期(componentWillMount,componentDidMount),返回 shouldUpdate
    shouldUpdate = resumeMountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
  }
复制代码

一、resumeMountClassInstance

作用:

复用 ClassComponent 实例,更新 propsstate ,调用生命周期API— componentWillMount()componentDidMount() ,最终返回 shouldUpdate:boolean

源码:

//复用 class 实例,更新 props/state,调用生命周期,返回 shouldUpdate
function resumeMountClassInstance(
  workInProgress: Fiber,
  ctor: any,
  newProps: any,
  renderExpirationTime: ExpirationTime,
): boolean {
  //获取 ClassComponent 实例
  const instance = workInProgress.stateNode;
  //获取已有的 props
  const oldProps = workInProgress.memoizedProps;
  //初始化 类实例 的 props
  instance.props = oldProps;
  //=====可跳过 context 相关代码====================================================
  const oldContext = instance.context;
  const contextType = ctor.contextType;
  let nextContext;
  if (typeof contextType === 'object' && contextType !== null) {
    nextContext = readContext(contextType);
  } else {
    const nextLegacyUnmaskedContext = getUnmaskedContext(
      workInProgress,
      ctor,
      true,
    );
    nextContext = getMaskedContext(workInProgress, nextLegacyUnmaskedContext);
  }
  //============================================================================

  const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
  //从开发角度上看,只要有调用getDerivedStateFromProps()或getSnapshotBeforeUpdate()
  //其中一个生命周期API,变量 hasNewLifecycles 就为 true
  const hasNewLifecycles =
    typeof getDerivedStateFromProps === 'function' ||
    typeof instance.getSnapshotBeforeUpdate === 'function';

  // Note: During these life-cycles, instance.props/instance.state are what
  // ever the previously attempted to render - not the "current". However,
  // during componentDidUpdate we pass the "current" props.

  // In order to support react-lifecycles-compat polyfilled components,
  // Unsafe lifecycles should not be invoked for components using the new APIs.
  //如果没有用新的生命周期的方法,则执行componentWillReceiveProps()
  //也就是说,如果有getDerivedStateFromProps()或getSnapshotBeforeUpdate(),就不调用componentWillReceiveProps方法了
  if (
    !hasNewLifecycles &&
    (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
      typeof instance.componentWillReceiveProps === 'function')
  ) {
    if (oldProps !== newProps || oldContext !== nextContext) {
      callComponentWillReceiveProps(
        workInProgress,
        instance,
        newProps,
        nextContext,
      );
    }
  }
  //设置 hasForceUpdate 为 false
  resetHasForceUpdateBeforeProcessing();
  //====更新 updateQueue,获取新 state,与mountClassInstance中的callComponentWillMount的下面逻辑相同,不再赘述================================
  const oldState = workInProgress.memoizedState;
  let newState = (instance.state = oldState);
  let updateQueue = workInProgress.updateQueue;
  if (updateQueue !== null) {
    processUpdateQueue(
      workInProgress,
      updateQueue,
      newProps,
      instance,
      renderExpirationTime,
    );
    newState = workInProgress.memoizedState;
  }
  //====================================
  //如果新老 props 和 state 没有差别,并且没有 forceupdate 的情况,
  //那么组件就不更新
  if (
    oldProps === newProps &&
    oldState === newState &&
    !hasContextChanged() &&
    !checkHasForceUpdateAfterProcessing()
  ) {
    // If an update was already in progress, we should schedule an Update
    // effect even though we're bailing out, so that cWU/cDU are called.
    //由于current为 null,即第一次渲染,需要调用componentDidMount()
    if (typeof instance.componentDidMount === 'function') {
      workInProgress.effectTag |= Update;
    }
    //即 shouldUpdate 为 false
    return false;
  }
  //有调用getDerivedStateFromProps()的话,则执行对应的applyDerivedStateFromProps
  //componentWillReceiveProps()与「getDerivedStateFromProps()/getSnapshotBeforeUpdate()」是互斥关系
  //这边能执行,说明componentWillReceiveProps()就不执行
  if (typeof getDerivedStateFromProps === 'function') {
    applyDerivedStateFromProps(
      workInProgress,
      ctor,
      getDerivedStateFromProps,
      newProps,
    );
    newState = workInProgress.memoizedState;
  }
  //检查是否有 forceUpdate 和新老 props/state 的更新
  //只有当既没有 forceUpdate 又没有 props/state 的改变,shouldUpdate才会为 false
  const shouldUpdate =
    checkHasForceUpdateAfterProcessing() ||
    checkShouldComponentUpdate(
      workInProgress,
      ctor,
      oldProps,
      newProps,
      oldState,
      newState,
      nextContext,
    );
  //当有更新的时候,执行相应的生命周期方法——componentWillMount()和componentDidMount()
  if (shouldUpdate) {
    // In order to support react-lifecycles-compat polyfilled components,
    // Unsafe lifecycles should not be invoked for components using the new APIs.
    if (
      !hasNewLifecycles &&
      (typeof instance.UNSAFE_componentWillMount === 'function' ||
        typeof instance.componentWillMount === 'function')
    ) {
      startPhaseTimer(workInProgress, 'componentWillMount');
      if (typeof instance.componentWillMount === 'function') {
        instance.componentWillMount();
      }
      if (typeof instance.UNSAFE_componentWillMount === 'function') {
        instance.UNSAFE_componentWillMount();
      }
      stopPhaseTimer();
    }
    if (typeof instance.componentDidMount === 'function') {
      workInProgress.effectTag |= Update;
    }
  } else {
    // If an update was already in progress, we should schedule an Update
    // effect even though we're bailing out, so that cWU/cDU are called.
    //有一个疑问——为什么不需要 update,还要执行componentDidMount方法来更新?
    //没明白注释写的cWU/cDU是啥意思
    if (typeof instance.componentDidMount === 'function') {
      workInProgress.effectTag |= Update;
    }

    // If shouldComponentUpdate returned false, we should still update the
    // memoized state to indicate that this work can be reused.
    //即使不需要 update,也会更新原有的 props/state,以保证复用
    //也没明白为啥
    workInProgress.memoizedProps = newProps;
    workInProgress.memoizedState = newState;
  }

  // Update the existing instance's state, props, and context pointers even
  // if shouldComponentUpdate returns false.
  //更新相关属性为最新的 props/state,无论是否有 update
  instance.props = newProps;
  instance.state = newState;
  instance.context = nextContext;
  //boolean
  return shouldUpdate;
}
复制代码

解析:

(1) 如果没有调用 getDerivedStateFromProps()getSnapshotBeforeUpdate() 的话,

则调用 componentWillReceiveProps()
(2) 更新 updateQueue ,获取 newState
(3) 如果新老 propsstate 没有差别,并且没有 forceupdate 的情况,那么组件就不更新, shouldUpdate=false

(4) 如果有调用 getDerivedStateFromProps() ,则执行它

注意:与 (1) 相比,则发现, componentWillReceiveProps() 与「 getDerivedStateFromProps() / getSnapshotBeforeUpdate() 」是 互斥关系 ,(4) 能执行,则 (1) 不会执行,反之一样

(5) 执行 checkShouldComponentUpdate() ,检查是否有 forceUpdate 和新老 props / state 的更新。

只有当既没有 forceUpdate 又没有 props / state 的改变, shouldUpdate 才会为 false
(6) 当 shouldUpdatetrue 时,判断是否执行 componentWillMount()componentDidMount()
(7) 当 shouldUpdatefalse 时,仍会判断执行 componentDidMount() 和更新 memoizedProps
(8) 更新 instance 上的 propsstate

(9) 最后返回 shouldUpdate

注意:

关于 getSnapshotBeforeUpdate() 的作用及用法,请参考:

zh-hans.reactjs.org/docs/react-…

里面部分 function 比较简单,先不讲,接下来讲下 checkShouldComponentUpdate()

二、checkShouldComponentUpdate

作用:

检查是否有 props / state 的更新,也是判断是否需要执行 shouldComponentUpdate() 的方法

源码:

function checkShouldComponentUpdate(
  workInProgress,
  ctor,
  oldProps,
  newProps,
  oldState,
  newState,
  nextContext,
) {
  const instance = workInProgress.stateNode;
  //如果有调用`shouldComponentUpdate()`的话,则返回执行该方法的结果
  if (typeof instance.shouldComponentUpdate === 'function') {
    startPhaseTimer(workInProgress, 'shouldComponentUpdate');
    const shouldUpdate = instance.shouldComponentUpdate(
      newProps,
      newState,
      nextContext,
    );
    stopPhaseTimer();

    //删除 dev 代码
    //返回true/false
    return shouldUpdate;
  }
  //如果是纯组件的话,用**浅比较**来比较 props/state
  if (ctor.prototype && ctor.prototype.isPureReactComponent) {
    return (
      //浅等于的判断
      !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
    );
  }

  return true;
}
复制代码

解析:

(1) 如果有调用 shouldComponentUpdate() 的话,则执行它,并返回执行结果,不再往下继续

(2) 如果是纯组件的话(PureComponent),则执行 shallowEqual() ,用 浅比较 来比较 props / state ,返回结果,不再往下继续

(3) 返回 true

注意:

关于 PureComponent 的浅比较判断,即 shallowEqual() ,将在下篇文章解析。

接下来,我们来谈论最后一种情况:

类实例存在,并且是多次渲染的情况:

//instance!==null&&current!==null
//当已经创建实例并且不是第一次渲染的话,调用更新的生命周期方法为componentWillUpdate,componentDidUpdate(),
  else {
    shouldUpdate = updateClassInstance(
      current,
      workInProgress,
      Component,
      nextProps,
      renderExpirationTime,
    );
  }
复制代码

三、updateClassInstance

作用:

当已经创建实例并且不是第一次渲染的话,调用更新的生命周期API— —componentWillUpdate/componentDidUpdate()

源码:

// Invokes the update life-cycles and returns false if it shouldn't rerender.
function updateClassInstance(
  current: Fiber,
  workInProgress: Fiber,
  ctor: any,
  newProps: any,
  renderExpirationTime: ExpirationTime,
): boolean {
  const instance = workInProgress.stateNode;

  const oldProps = workInProgress.memoizedProps;
  instance.props =
    workInProgress.type === workInProgress.elementType
      ? oldProps
      : resolveDefaultProps(workInProgress.type, oldProps);

  const oldContext = instance.context;
  const contextType = ctor.contextType;
  let nextContext;
  if (typeof contextType === 'object' && contextType !== null) {
    nextContext = readContext(contextType);
  } else {
    const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
  }

  const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
  const hasNewLifecycles =
    typeof getDerivedStateFromProps === 'function' ||
    typeof instance.getSnapshotBeforeUpdate === 'function';

  // Note: During these life-cycles, instance.props/instance.state are what
  // ever the previously attempted to render - not the "current". However,
  // during componentDidUpdate we pass the "current" props.

  // In order to support react-lifecycles-compat polyfilled components,
  // Unsafe lifecycles should not be invoked for components using the new APIs.
  if (
    !hasNewLifecycles &&
    (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
      typeof instance.componentWillReceiveProps === 'function')
  ) {
    if (oldProps !== newProps || oldContext !== nextContext) {
      callComponentWillReceiveProps(
        workInProgress,
        instance,
        newProps,
        nextContext,
      );
    }
  }

  resetHasForceUpdateBeforeProcessing();

  const oldState = workInProgress.memoizedState;
  let newState = (instance.state = oldState);
  let updateQueue = workInProgress.updateQueue;
  if (updateQueue !== null) {
    processUpdateQueue(
      workInProgress,
      updateQueue,
      newProps,
      instance,
      renderExpirationTime,
    );
    newState = workInProgress.memoizedState;
  }

  if (
    oldProps === newProps &&
    oldState === newState &&
    !hasContextChanged() &&
    !checkHasForceUpdateAfterProcessing()
  ) {
    // If an update was already in progress, we should schedule an Update
    // effect even though we're bailing out, so that cWU/cDU are called.

    //注意这里与 resumeMountClassInstance() 不一样
    //updateClassInstance():componentDidUpdate/getSnapshotBeforeUpdate
    //resumeMountClassInstance():componentDidMount
    if (typeof instance.componentDidUpdate === 'function') {
      if (
        oldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.effectTag |= Update;
      }
    }
    if (typeof instance.getSnapshotBeforeUpdate === 'function') {
      if (
        oldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.effectTag |= Snapshot;
      }
    }
    return false;
  }

  if (typeof getDerivedStateFromProps === 'function') {
    applyDerivedStateFromProps(
      workInProgress,
      ctor,
      getDerivedStateFromProps,
      newProps,
    );
    newState = workInProgress.memoizedState;
  }

  const shouldUpdate =
    checkHasForceUpdateAfterProcessing() ||
    checkShouldComponentUpdate(
      workInProgress,
      ctor,
      oldProps,
      newProps,
      oldState,
      newState,
      nextContext,
    );

  if (shouldUpdate) {
    // In order to support react-lifecycles-compat polyfilled components,
    // Unsafe lifecycles should not be invoked for components using the new APIs.
    //此处也与resumeMountClassInstance() 不同
    //updateClassInstance():componentWillUpdate/componentDidUpdate/getSnapshotBeforeUpdate
    //resumeMountClassInstance():componentWillMount/componentDidMount
    if (
      !hasNewLifecycles &&
      (typeof instance.UNSAFE_componentWillUpdate === 'function' ||
        typeof instance.componentWillUpdate === 'function')
    ) {
      startPhaseTimer(workInProgress, 'componentWillUpdate');
      if (typeof instance.componentWillUpdate === 'function') {
        instance.componentWillUpdate(newProps, newState, nextContext);
      }
      if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
        instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
      }
      stopPhaseTimer();
    }
    if (typeof instance.componentDidUpdate === 'function') {
      workInProgress.effectTag |= Update;
    }
    if (typeof instance.getSnapshotBeforeUpdate === 'function') {
      workInProgress.effectTag |= Snapshot;
    }
  } else {
    // If an update was already in progress, we should schedule an Update
    // effect even though we're bailing out, so that cWU/cDU are called.
    if (typeof instance.componentDidUpdate === 'function') {
      if (
        oldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.effectTag |= Update;
      }
    }
    if (typeof instance.getSnapshotBeforeUpdate === 'function') {
      if (
        oldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.effectTag |= Snapshot;
      }
    }

    // If shouldComponentUpdate returned false, we should still update the
    // memoized props/state to indicate that this work can be reused.
    workInProgress.memoizedProps = newProps;
    workInProgress.memoizedState = newState;
  }

  // Update the existing instance's state, props, and context pointers even
  // if shouldComponentUpdate returns false.
  instance.props = newProps;
  instance.state = newState;
  instance.context = nextContext;

  return shouldUpdate;
}
复制代码

解析:

该方法与 resumeMountClassInstance() 逻辑类似,就不再赘述了,但注意下两者调用生命周期 API 的不同:

在这三种情况执行后, updateFunctionComponent() 最后执行了 finishClassComponent() 方法,来判断是否需要 render

//判断是否执行 render,并返回 render 下的第一个 child
  const nextUnitOfWork = finishClassComponent(
    current,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderExpirationTime,
  );
复制代码

接下来,我们就看下该方法

四、finishClassComponent

作用:

判断是否执行 render() ,并返回 render 下的第一个 child

源码:

//判断是否执行 render,并返回 render 下的第一个 child
function finishClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  shouldUpdate: boolean,
  hasContext: boolean,
  renderExpirationTime: ExpirationTime,
) {
  // Refs should update even if shouldComponentUpdate returns false
  //无论是否更新 props/state,都必须更新 ref 指向
  markRef(current, workInProgress);
  //判断是否有错误捕获
  const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;
  //当不需要更新/更新完毕,并且没有出现 error 的时候
  if (!shouldUpdate && !didCaptureError) {
    // Context providers should defer to sCU for rendering
    if (hasContext) {
      invalidateContextProvider(workInProgress, Component, false);
    }
    //跳过该class 上的节点及所有子节点的更新,即跳过调用 render 方法
    return bailoutOnAlreadyFinishedWork(
      current,
      workInProgress,
      renderExpirationTime,
    );
  }

  const instance = workInProgress.stateNode;

  // Rerender
  ReactCurrentOwner.current = workInProgress;
  let nextChildren;
  //getDerivedStateFromError是生命周期api,作用是捕获 render error,详情请看:
  //https://zh-hans.reactjs.org/docs/react-component.html#static-getderivedstatefromerror
  if (
    didCaptureError &&
    typeof Component.getDerivedStateFromError !== 'function'
  ) {
    // If we captured an error, but getDerivedStateFrom catch is not defined,
    // unmount all the children. componentDidCatch will schedule an update to
    // re-render a fallback. This is temporary until we migrate everyone to
    // the new API.
    // TODO: Warn in a future release.
    //如果出现 error 但是开发者没有调用getDerivedStateFromError的话,就中断渲染
    nextChildren = null;

    if (enableProfilerTimer) {
      stopProfilerTimerIfRunning(workInProgress);
    }
  }
  //否则重新渲染
  else {
    //删除了 dev 代码
    if (__DEV__) {

    } else {
      nextChildren = instance.render();
    }
  }

  // React DevTools reads this flag.
  workInProgress.effectTag |= PerformedWork;
  if (current !== null && didCaptureError) {
    // If we're recovering from an error, reconcile without reusing any of
    // the existing children. Conceptually, the normal children and the children
    // that are shown on error are two different sets, so we shouldn't reuse
    // normal children even if their identities match.
    //强制重新计算 children,因为当出错时,是渲染到节点上的 props/state 出现了问题,所以不能复用,必须重新 render
    forceUnmountCurrentAndReconcile(
      current,
      workInProgress,
      nextChildren,
      renderExpirationTime,
    );
  } else {
    // 将 ReactElement 变成fiber对象,并更新,生成对应 DOM 的实例,并挂载到真正的 DOM 节点上
    reconcileChildren(
      current,
      workInProgress,
      nextChildren,
      renderExpirationTime,
    );
  }

  // Memoize state using the values we just used to render.
  // TODO: Restructure so we never read values from the instance.
  workInProgress.memoizedState = instance.state;

  // The context might have changed so we need to recalculate it.
  if (hasContext) {
    invalidateContextProvider(workInProgress, Component, true);
  }
  //返回 render 下的第一个节点
  return workInProgress.child;
}
复制代码

解析:

(1) 无论是否更新 props / state ,都必须更新 ref 指向,故执行 markRef()

function markRef(current: Fiber | null, workInProgress: Fiber) {
  const ref = workInProgress.ref;
  if (
    (current === null && ref !== null) ||
    (current !== null && current.ref !== ref)
  ) {
    // Schedule a Ref effect
    workInProgress.effectTag |= Ref;
  }
}
复制代码

(2) 判断是否有错误捕获,赋值给 didCaptureError
(3) 当不需要更新/更新完毕,并且没有捕获到 error 的时候,则执行 bailoutOnAlreadyFinishedWork() ,跳过该 ClassInstance 上的节点及所有子节点的更新,即跳过调用 render 方法

关于 bailoutOnAlreadyFinishedWork() 的讲解,请看:

React源码解析之workLoop

(4) 如果捕获到了 error ,并且开发者没有调用 getDerivedStateFromError 的话,就中断渲染,将 nextChildren 置为 null

关于 getDerivedStateFromError() 的讲解,请看:

zh-hans.reactjs.org/docs/react-…
(5) 如果没有捕获到 error 的话,则执行 instance.render() ,重新渲染,并返回 nextChildren

(6) 渲染后,如果捕获到 error ,则执行 forceUnmountCurrentAndReconcile() ,强制重新计算 children

否则,执行 reconcileChildren() ,将 ReactElement 变成 fiber 对象,并更新,生成对应 DOM 的实例,并挂载到真正的 DOM 节点上

(7) 最后,返回 render 下的第一个节点 workInProgress.child

finishClassComponent() 的流程图如下:

OK,至此 updateClassComponent() 就讲解结束,关于 PureComponent 的浅比较判断,即 shallowEqual() ,将在下篇文章解析。

(完)

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章