react 性能优化 - children如何避免不必要的渲染

一个例子
  1. Expensive作为 Input的children传入
  2. Input组件中触发state更新
import React, { useState } from 'react'
function Input({ children }) {
    const [num, updateNum] = useState(0);
    console.log('Input','-----');
    return (
        <>
             {
                updateNum(+e.target.value)
            }} />
            

num is {num}

{children} ); } function Expensive() { console.log('expensive render'); return

expensive

} export default function Diff() { console.log('Diff','-----'); return }
为什么fiberRoot在beginWork会命中bailoutOnAlreadyFinishedWork
  • 条件1: oldProps === newProps
  1. prepareFreshStack中根据root.current创建 workInProgress时, pendingProps的值为null
// prepareFreshStack
workInProgress = createWorkInProgress(root.current, null); 
  1. current.memoizedProps也为null 所以条件成立
 // beginWork
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps; // null 
  1. 为什么 current.memoizedProps为null呢? 因为在 performUnitOfWork中, 当前fiber beginWork结束之后会将 pendingProps(本次更新的属性) 复制给 memoizedProps(上次更新时用的属性)
function performUnitOfWork(unitOfWork: Fiber): void {
  // 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.
  const current = unitOfWork.alternate;
  setCurrentDebugFiberInDEV(unitOfWork);

  let next;
  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
  }

  resetCurrentDebugFiberInDEV();
  // 当前fiber beginWork结束之后会将 pendingProps(本次更新的属性) 复制给 memoizedProps(上次更新时用的属性)
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

  ReactCurrentOwner.current = null;
} 
  • 条件2: rootFiber.lanes不存在于 renderLanes中
  1. 因为产生更新的是Input组件
 // ...
  } else if (!includesSomeLane(renderLanes, updateLanes)) {
      didReceiveUpdate = false; 
为什么Diff会命中bailoutOnAlreadyFinishedWork
  • Diff beginWork中, props没有发生变化?
  1. 在 rootFiber命中 bailoutOnAlreadyFinishedWork后,会cloneChildFibers,从而workInProgress.pendingProps === current.pendingProps成立
// cloneChildFibers
// 注意 这里的pendingProps传递的是 current上的pendingProps
let newChild = createWorkInProgress(currentChild, currentChild.pendingProps); 
// createWorkInProgress
const createFiber = function ( tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode, ): Fiber {
  return new FiberNode(tag, pendingProps, key, mode);
};

// FiberNode
this.pendingProps = pendingProps; 
  1. 而在 上次 workInprogress(本次的current) beginWork结束之后, 会给 memoizedProps赋值为pendingProps,从而``workInProgress.pendingProps === current.memoizedProps 成立`
// performUnitOfWork
unitOfWork.memoizedProps = unitOfWork.pendingProps; 
  • 为什么Diff组件的fiber上没有 Input组件产生更新优先级?
  1. Input产生的更新会合并到 祖先的childLanes上(即这里的Diff和FiberRoot都会被合并, 而Diff组件自己的lanes不会改变,lanes发生改变的只有Input组件对应fiber
 // markUpdateLaneFromFiberToRoot
  // 更新产生更新的fiber的lanes
  sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
  
  while (parent !== null) {
    // 将当前的更新产生的lane 合并的parent的childLanes上
    parent.childLanes = mergeLanes(parent.childLanes, lane);
    alternate = parent.alternate;
    if (alternate !== null) {
      alternate.childLanes = mergeLanes(alternate.childLanes, lane);
    } else {
      if (true) {
        if ((parent.flags & (Placement | Hydrating)) !== NoFlags) {
          warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
        }
      }
    }
    node = parent;
    parent = parent.return;
  } 
  • 基于上面两个条件,所以Diff组件命中了 bailoutOnAlreadyFinishedWork,从而跳过当前fiber的beginWork阶段(所以Diff这个函数只会执行一次). 但是由于子树中的Input存在更新(即workInProgress.childlans中存在updateLane),所以直接cloneChildFibers并返回子fiber(即这里的Input的fiber节点)
为什么Expensive只渲染了一次
  1. 首先在调和Input的children时(暂时不考虑Fragment) , Expensive fiberprops没有发生变化(因为 Diff组件命中了 bailoutOnAlreadyFinishedWork),并且lanes也不在renderLanes上,所以会触发 bailoutOnAlreadyFinishedWork
if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;
    // oldProps === newProps true
    if (
      oldProps !== newProps ||
      hasLegacyContextChanged() ||
      // Force a re-render if the implementation changed due to hot reload:
      (__DEV__ ? workInProgress.type !== current.type : false)
    ) {
      didReceiveUpdate = true;
    } else if (!includesSomeLane(renderLanes, updateLanes)) {
      didReceiveUpdate = false;
	  // ...
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); 
  1. bailoutOnAlreadyFinishedWork的逻辑中会检查children.childLanes(即children的children是否存在更新), 如果不存在更新,则直接跳过子树(这里直接跳过子树),否则继续调和子树
 // Check if the children have any pending work.
  // 如果不存在更新,则直接跳过子树
  if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
    // The children don't have any work either. We can skip them.
    // TODO: Once we add back resuming, we should check if the children are
    // a work-in-progress set. If so, we need to transfer their effects.
    return null;
  } else {
	// 如果子树中存在更新,则继续调和子树
    // This fiber doesn't have work, but its subtree does. Clone the child
    // fibers and continue.
    cloneChildFibers(current, workInProgress); 

你可能感兴趣的:(react.js,javascript,前端)