likes
comments
collection
share

👊拳拳到肉,深入源码,理解react如何渲染--shouldComponentUpdate篇

作者站长头像
站长
· 阅读数 12

在讲本文之前,附一张shouldComponentUpdate的调用栈,本文所有的内容都是基于这个调用栈:

👊拳拳到肉,深入源码,理解react如何渲染--shouldComponentUpdate篇

shouldComponentUpdate

  • 在发出fiber更新的过程,对于classComponent会调用updateClassinstance的方法,而在该方法中,就会调用生命周期shouldComponentUpdate方法
  • 如果shouldComponentUdate返回false,就不会对当前的fiber执行render函数,也就不会生成新的element节点,也就是传给子节点的props也不会更新
function updateClassComponent(current, workInProgress, Component, nextProps, renderLanes) {
  var instance = workInProgress.stateNode;
  var shouldUpdate;
  if (instance === null) {
    //当前组件没有实例化
    //...
    shouldUpdate = true;
  } else if (current === null) {
    // 当前组件第一次渲染,没有对应的current
    shouldUpdate = resumeMountClassInstance(workInProgress, Component, nextProps, renderLanes);
  } else {
    // 处理updateQueue
    //在此处会调用updateClassinstance
    shouldUpdate = updateClassInstance(current, workInProgress, Component, nextProps, renderLanes);
  }

}

//updateClassInstance
function updateClassInstance(current, workInProgress, ctor, newProps, renderLanes) {
  var instance = workInProgress.stateNode;
  var oldProps = workInProgress.type === workInProgress.elementType ? unresolvedOldProps : resolveDefaultProps(workInProgress.type, unresolvedOldProps);

  var oldState = workInProgress.memoizedState;
  var newState = instance.state = oldState;
  processUpdateQueue(workInProgress, newProps, instance, renderLanes);
  newState = workInProgress.memoizedState;

  var shouldUpdate = checkHasForceUpdateAfterProcessing() || checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext) || // TODO: In some cases, we'll end up checking if context has changed twice,

    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_componentWillUpdate === 'function' || typeof instance.componentWillUpdate === 'function')) {
      if (typeof instance.componentWillUpdate === 'function') {
        instance.componentWillUpdate(newProps, newState, nextContext);
      }

      if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
        instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
      }
    }

    if (typeof instance.componentDidUpdate === 'function') {
      workInProgress.flags |= Update;
    }

    if (typeof instance.getSnapshotBeforeUpdate === 'function') {
      workInProgress.flags |= Snapshot;
    }
  } else {
    //...

    // 虽然不render,但是props还是要给新的?
    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;
}
  • 虽然当前的render被阻止了,但是props,和state还是照样更新
function checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext) {
  var instance = workInProgress.stateNode;

  if (typeof instance.shouldComponentUpdate === 'function') {
    var shouldUpdate = instance.shouldComponentUpdate(newProps, newState, nextContext);
		//...
    return shouldUpdate;
  }

  if (ctor.prototype && ctor.prototype.isPureReactComponent) {
    return !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState);
  }

  return true;
}
  • checkShouldComponentUpdate中会真正调用shouldComponentUpdate函数,其返回值会决定是否执行render函数
  • 网上的文章说如果shouldComponentUpdate函数返回false,整个组件就不更新了。这是不对的,不是不更新,而是不执行render函数而已
  • 如果组件中没有定义shouldComponentUpdate函数,紧接着就判断组件是否为pureComponent,如果是的话,就对propsState做浅比较,浅比较的结果也将会影响render函数是否得到执行
  • 所以如果没有特殊需求,可以直接使用pureComponent来做性能优化

有一个小问题,如果父组件阻断了更新,不执行render,是否会影响子组件的更新? 答:可能会,也可能不会

说得这么玄,具体情况怎么回事呢?我们来看源码。当shouldComponentUpdate返回false之后发生了什么

    shouldUpdate = updateClassInstance(current, workInProgress, Component, nextProps, renderLanes);

  // render,生成子fibers,运用diff算法
  var nextUnitOfWork = finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderLanes);

  • updateClassInstance函数会返回最初的shouldupdate,然后将shouldupdate传递给finishClassComponent做参数
  • 我们来看看finishClassComponent里面是怎么处理这个参数的
function finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderLanes) {
  // Refs should update even if shouldComponentUpdate returns false
  markRef(current, workInProgress);
  var didCaptureError = (workInProgress.flags & DidCapture) !== NoFlags;

  if (!shouldUpdate && !didCaptureError) {
    // Context providers should defer to sCU for rendering
    if (hasContext) {
      invalidateContextProvider(workInProgress, Component, false);
    }

    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }

  //实例的render函数
  instance.render();

}
  • 在第6行中,会对shouldUpdate做判断, 如果不应该更新,以及不是错误边界,就会返回bailoutOnAlreadyFinishedWork的执行结果

didCaptureError是错误边界的一个判断标准,错误边界是当组件有错误的时候,由suspense组件拦截错误,这个时候就会给拦截错误的组件成为错误边界。这里做了下判断,用意是错误边界的一定不会遍历子组件,所以就没必要执行bailoutOnAlreadyFinishedWork函数了

  • bailoutOnAlreadyFinishedWork函数的逻辑简单来说就是判断子组件是否有更新,如果没有就返回null,如果有更新,就返回current clone的子组件
  • 返回null就意味着停止子组件的更新遍历,直接对该组件completework
  • 返回clone的子组件就意味着对子组件继续遍历,继续调用beginwork
function workLoopSync() {
  // Already timed out, so perform work without checking if we need to yield.
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

function performUnitOfWork(unitOfWork) {
  // The current, flushed, state of this fiber is the alternate. Ideally
  // nothing should rely on this, but relying on it here means that we don't
  // need an additional field on the work in progress.
  var current = unitOfWork.alternate;
  var next;

  next = beginWork$1(current, unitOfWork, subtreeRenderLanes);

  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    // unitOfWork是最后一个子节点,结束的beginWork的那个节点
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

  ReactCurrentOwner$2.current = null;
}
  • performUnitOfWork函数是render阶段的入口函数,会对组件tree做递归遍历
  • 会根据next的值来决定是否调用completework

总结

这篇文章讲了shouldComponentUpdate的更新逻辑,以及使用方法,对子组件更新的影响等等