React.Component VS React.PureComponent

源码解读

从判断类组件更新的源码开始。
updateClassComponent()
触发时机:整个项目的任何位置的state, props, context更改,都会导致该函数被触发,且一个类组件就会触发一次,所以其触发的次数就是类组件的数量。
精简后的源码:

/**
* current: 已经用于渲染的fiber
* workInProgress: 正处于更新阶段的fiber
* Component: 当前的组件,组件的代码
* nextProps: 新的props
* renderExpirationTime: 更新的过期时间
*/
  function updateClassComponent(current, workInProgress, Component, nextProps, renderExpirationTime) {
    /**
     * stateNode: {
     *  context: {},
     *  refs: {},
     *  props: {},
     *  state: {},
     *  updater: {
     *    enqueueForceUpdate: fn,
     *    enqueueReplaceState: fn,
     *    enqueueSetState: fn,
     *    isMounted: fn
     *  }
     * }
    */
    var instance = workInProgress.stateNode; //是当前组件的一些信息。
    var shouldUpdate;

    if (instance === null) {
      // Logic for other exceptional cases
    } else {
      // 关注这里,是否要更新由updateClassInstance方法处理
      shouldUpdate = updateClassInstance(current, workInProgress, Component, nextProps, renderExpirationTime);
    }
    // 轮转下一个要工作的单元
    var nextUnitOfWork = finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderExpirationTime);
    return nextUnitOfWork;
  }

updateClassInstance()方法:

function updateClassInstance(current, workInProgress, ctor, newProps, renderExpirationTime) {
    var instance = workInProgress.stateNode;
    cloneUpdateQueue(current, workInProgress); // 拷贝一份更新队列
    var oldProps = workInProgress.memoizedProps; // 旧的props
    instance.props = workInProgress.type === workInProgress.elementType ? oldProps : resolveDefaultProps(workInProgress.type, oldProps);
  
    var getDerivedStateFromProps = ctor.getDerivedStateFromProps;
    var hasNewLifecycles = typeof getDerivedStateFromProps === 'function' || typeof instance.getSnapshotBeforeUpdate === 'function'; // Note: During these life-cycles, instance.props/instance.state are what
    if (!hasNewLifecycles && (typeof instance.UNSAFE_componentWillReceiveProps === 'function' || typeof instance.componentWillReceiveProps === 'function')) {
      // 触发生命周期ComponentWillReceiveProps
      if (oldProps !== newProps || oldContext !== nextContext) {
        callComponentWillReceiveProps(workInProgress, instance, newProps, nextContext);
      }
    }
    
    resetHasForceUpdateBeforeProcessing();
    var oldState = workInProgress.memoizedState; // 旧的state
    var newState = instance.state = oldState; // 即将存放新的state,这里还是旧值

    // 操作更新队列,同时将新的state更新到workInProgress的memoizedState节点上。
    processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
    newState = workInProgress.memoizedState; // 现在是最新的state了

    // 后面解释这里为什么没有返回
    // checkHasForceUpdateAfterProcessing()就是返回代码中是不是有调用强制刷新
    if (oldProps === newProps && oldState === newState && !hasContextChanged() && !checkHasForceUpdateAfterProcessing()) {
        // other
      return false;
    }

    if (typeof getDerivedStateFromProps === 'function') { //触发getDerivedStateFromProps()
      applyDerivedStateFromProps(workInProgress, ctor, getDerivedStateFromProps, newProps);
      newState = workInProgress.memoizedState;
    }
    
    // 如果代码中有强制更新操作,则不用任何判断都会导致重新render
    // 关注这里
    var shouldUpdate = checkHasForceUpdateAfterProcessing() || checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext);

    if (shouldUpdate) {
      // 需要更新
      if (!hasNewLifecycles && (typeof instance.UNSAFE_componentWillUpdate === 'function' || typeof instance.componentWillUpdate === 'function')) {
          // 触发声明周期componentWillUpdate
        if (typeof instance.componentWillUpdate === 'function') {
          instance.componentWillUpdate(newProps, newState, nextContext);
        }
  
        if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
          instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
        }
      }
    } else {
       // 无需更新的操作
  
      workInProgress.memoizedProps = newProps;
      workInProgress.memoizedState = newState;
    } // Update the existing instance's state, props, and context pointers even
    // if shouldComponentUpdate returns false.

    // instance就是当前的组件,将新的状态更新到组件的对应节点上
    instance.props = newProps;
    instance.state = newState;
    instance.context = nextContext;
    return shouldUpdate;
  }

这段代码有个地方要单独说明一下

    if (oldProps === newProps && oldState === newState && !hasContextChanged() && !checkHasForceUpdateAfterProcessing()) {
        // other
      return false;
    }

如果满足上面的条件,那么组件也是不会更新的,现在,假设我有2个组件,父组件有两个state,子组件接收其中的一个,当点击父组件按钮时,更新未被子组件接收的那一个state,发现子组件的render也会触发。

    class Parent extends React.Component {
        state = {
            count: 0,
            name: 'home'
        }
        onClick = () => {
            this.setState({
                count: this.state.count+1
            })
        }
        render() {
            return (
                
) } } class Child extends React.Component { render() { console.log('我被触发了') return (

{this.props.name}

) } }

按照源码的逻辑,组件的stateprops没有更新,也没有context变化,更没有设置强制刷新,那么应该满足条件直接返回false了呀。断点调试发现oldProps === newPropsfalse

截屏2020-10-22 15.56.22.png

继续读源码,发现oldPropsnewProps比较就是workInProgress.memoizedPropsworkInProgress.pendingProps比较,这两个对象的引用地址是不同的,所以这个if条件一般情况下不成立。(都等于null则成立)
接着往下看checkShouldComponentUpdate()方法:

  function checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext) {
    var instance = workInProgress.stateNode;
    // 如果组件显示使用了shouldComponentUpdate,则组件是否需要更新由组件自身决定
    if (typeof instance.shouldComponentUpdate === 'function') {
      var shouldUpdate = instance.shouldComponentUpdate(newProps, newState, nextContext);
      return shouldUpdate;
    }
    // 重点来了,PureComponent和Component的区别就在这里了
    if (ctor.prototype && ctor.prototype.isPureReactComponent) {
      return !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState);
    }
    // 不是PureComponent,始终返回true
    return true;
  }

如果是React.PureComponent,则会对该组件的新旧state和新旧props做一个浅比较,注意,只是该组件的props。而如果是React.Component,则只要是父组件的重新render,一定会引起所有子组件的重新render(没有手动控制shouldComponentUpdate),这就是React.PureComponentReact.Component唯一的区别了。

不规范的写法可能导致React.PureComponent 无法正常更新

React.PureComponent什么时候会更新,则完全取决于shallowEqual()会怎么处理了,我遇到过一些场景,本来是希望React.PureComponent可以更新,但是却没有更新,为了保险起见,直接替换为React.Component了,这样做虽然没有问题,但如果理解了React.PureComponent如何更新对于我们理解React行为也是有帮助的,继续看一下shallowEqual()的源码:

  // objectIs就是Object.is, 用法参见https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is
  function shallowEqual(objA, objB) {
    if (objectIs(objA, objB)) {
      return true;
    }

    if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
      return false;
    }

    var keysA = Object.keys(objA);
    var keysB = Object.keys(objB);

    if (keysA.length !== keysB.length) {
      return false;
    } // Test for A's keys different from B.


    for (var i = 0; i < keysA.length; i++) {
      if (!hasOwnProperty$2.call(objB, keysA[i]) || !objectIs(objA[keysA[i]], objB[keysA[i]])) {
        return false;
      }
    }

    return true;
  }

浅比较对象的过程如下:

  1. Object.is比较引用地址是否发生变化。对于props和state,他们前后的引用地址是不相等的,所以这里一定为false.
  2. 如果是非对象或者null,则返回false,一般不会出现这种情况。
  3. 比较前后两次对象的键的长度,如果不一样,即有新增或者删除属性,则返回false
  4. 遍历对象,比较前后两次对象的键名,如果发生了变化,则返回false。浅比较比较前后两次键值,如果不相等,则返回false

案例1:未浅拷贝对象,导致视图无法更新,比如:

state = {
    person: {name: 'hello'}
}
this.setState({
    person: Object.assign(this.state.person, {
        name: 'jack'
    })
})

按上面的比较过程,1,2,3都不满足,走到第4步,发现前后两次的键名不变,且键值也相等,则判断为相等,最后判断为无需更新。不过对象上的值确实变了,所以如果是继承自React.Component的组件,仍然可以正常看到组件更新。

判断是否需要“更新”后,接下来的工作

到这里我们已经看到了部分生命周期被执行,不过render()方法还未看到,可以沿着updateClassComponent继续往下看,当返回了shouldUpdate标志位之后,控制权交给了finishClassComponent

function finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderExpirationTime) {
    // Refs should update even if shouldComponentUpdate returns false
    markRef(current, workInProgress);
    var didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;
    
    if (!shouldUpdate && !didCaptureError) {
      // 如果无需更新,并且当前未发现错误
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderExpirationTime);
    }
    var instance = workInProgress.stateNode; // Rerender
    ReactCurrentOwner$1.current = workInProgress;
    var nextChildren;

    if (didCaptureError && typeof Component.getDerivedStateFromError !== 'function') {
     //....
    } else {
      nextChildren = instance.render(); // 调用组件的render()方法
    }
    workInProgress.memoizedState = instance.state; // The context might have changed so we need to recalculate it.
    return workInProgress.child;
  }

再往后还有一段处理过程,才会到componentDidUpdate()阶段,有兴趣的同学可以自己去看看。

shouldUpdate的作用到这里就结束了,也就是说,React.ComponentReact.PureComponent的区别也就探讨结束了,再总结一下它们的区别:

对于可能引起更新的动作:

  1. state更新
  2. 自身的props更新
  3. 非自身的props更新,但引起了父组件更新。

对于React.Component,在不手动控制shouldComponentUpdate()的情况下,上述三个条件任意一个发生的情况下,有:

  1. componentWillReceiveProps(UNSAFE_componentWillReceiveProps) 或者getDerivedStateFromProps
  2. shouldComponentUpdate
  3. componentWillUpdate(UNSAFE_componentWillUpdate) 或者getSnapshotBeforeUpdate()
  4. render()
  5. componentDidUpdate()

对于React.Component,在不手动控制shouldComponentUpdate()的情况下,1,2两个条件变化和React.Component一样,但条件3表现和React.Component不一样:

  1. componentWillReceiveProps(UNSAFE_componentWillReceiveProps) 或者getDerivedStateFromProps
  2. shouldComponentUpdate

差点被漏掉的context

updateClassInstance中:

    processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
    ....
    var shouldUpdate = checkHasForceUpdateAfterProcessing() || checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext);

checkHasForceUpdateAfterProcessing():

  function checkHasForceUpdateAfterProcessing() {
    return hasForceUpdate;
  }

hasForceUpdate是一个全局变量,而hasForceUpdate的值在调用checkHasForceUpdateAfterProcessing()之前会修改,也就是在processUpdateQueue()里面,调用了getStateFromUpdate()方法:

  function getStateFromUpdate(workInProgress, queue, update, prevState, nextProps, instance) {
    switch (update.tag) {
      case ReplaceState:
        {}

      case CaptureUpdate:
        { }

      case UpdateState: // update.tag: 0
        { }

      case ForceUpdate: // update.tag: 2
        {
          hasForceUpdate = true;
          return prevState;
        }
    }

    return prevState;
  }

当更新来自于context时,会将hasForceUpdate变量置为true,最后导致shouldUpdatetrue

从源码也可以发现,context的变更对于组件是Component还是PureComponent是没有关系的。

最后

意外在getStateFromUpdate()中发现了setState的相关机制,下一篇文章就从这里开始吧。

你可能感兴趣的:(React.Component VS React.PureComponent)