美文网首页
React.Component VS React.PureCom

React.Component VS React.PureCom

作者: 大柚子08 | 来源:发表于2020-10-23 17:03 被阅读0次

    源码解读

    从判断类组件更新的源码开始。
    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 (
                    <div>
                        <button onClick={this.onClick} >add count</button>
                        <Child name={this.state.name}/>
                    </div>
                )
            }
        }
    
         class Child extends React.Component {
            render() {
                console.log('我被触发了')
                return (
                    <div>
                        <p>{this.props.name}</p>
                    </div>
                )
            }
        }
    

    按照源码的逻辑,<Child />组件的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.PureCom

          本文链接:https://www.haomeiwen.com/subject/uoeumktx.html