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

React源码解析之updateClassComponent(下)

作者头像
进击的小进进
发布2020-02-13 10:32:57
7730
发布2020-02-13 10:32:57
举报
文章被收录于专栏:前端干货和生活感悟

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

本文将先讨论第二种情况: 类实例存在,但 current 为 null,即第一次渲染的情况

代码语言:javascript
复制
//第一次渲染
  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

源码:

代码语言:javascript
复制
//复用 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()的作用及用法,请参考: https://zh-hans.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate

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

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

源码:

代码语言:javascript
复制
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(),将在下篇文章解析。

接下来,我们来谈论最后一种情况: 类实例存在,并且是多次渲染的情况

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

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

源码:

代码语言:javascript
复制
// 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

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

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

四、finishClassComponent 作用: 判断是否执行render(),并返回render下的第一个child

源码:

代码语言:javascript
复制
//判断是否执行 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()

代码语言:javascript
复制
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()的讲解,请看: https://zh-hans.reactjs.org/docs/react-component.html#static-getderivedstatefromerror

(5) 如果没有捕获到error的话,则执行instance.render(),重新渲染,并返回nextChildren (6) 渲染后,如果捕获到error,则执行forceUnmountCurrentAndReconcile(),强制重新计算children; 否则,执行reconcileChildren(),将ReactElement变成fiber对象,并更新,生成对应 DOM 的实例,并挂载到真正的 DOM 节点上 (7) 最后,返回 render 下的第一个节点workInProgress.child

finishClassComponent()的流程图如下:

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

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


(完)

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

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

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

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

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