React函数组件性能优化三部曲(二)

在上一章节,我们讲解了普通函数组件更新的基本流程,也知道了一个组件修改状态触发的更新,它的子组件也跟着重新渲染的原因。

所以本节就将从React.memo方法来解析函数组件的性能优化处理。

1,案例准备

这是之前的测试案例:

js 复制代码
// MyFun.js
import { useState } from 'react'
import Child from './Child'

export default function MyFun(props) {
  console.log('MyFun组件运行了')
  const [count, setCount] = useState(1)
  function handleClick() {
    setCount(count + 1)
  }
  return (
    <div className='MyFun'>
      <div>state: {count}</div>
      <Child name='Child'></Child>
      <button onClick={handleClick}>更新</button>
    </div>
  )
}
js 复制代码
// Child.js
export default function Child(props) {
  console.log('Child组件运行了')
  return (
    <div className='Child'>
      <div>Child子组件</div>
      <div>name: {props.name}</div>
    </div>
  )
}

每当我们点击MyFun组件中的更新按钮时,Child子组件都会重新渲染一次:

我们点击了三次更新按钮,Child子组件也跟着父组件重新渲染了三次,这就是普通的函数组件更新。

React.memo方法

React.memo方法的作用:创建函数组件中的纯组件,在 props 没有发生变化的情况下跳过重新渲染re-render

此方法常用于普通函数组件或者forwardRef组件。

下面我们使用React.memo方法来优化child组件。

js 复制代码
// MyFun.js
import { useState } from 'react'
import NewChild from './NewChild'

export default function MyFun(props) {
  console.log('MyFun组件运行了')
  const [count, setCount] = useState(1)
  function handleClick() {
    setCount(count + 1)
  }
  return (
    <div className='MyFun'>
      <div>state: {count}</div>
      <NewChild name='NewChild'></NewChild>
      <button onClick={handleClick}>更新</button>
    </div>
  )
}
js 复制代码
// Child.js
import React from 'react'

function Child(props) {
  console.log('Child组件运行了')
  return (
    <div className='Child'>
      <div>Child子组件</div>
      <div>name: {props.name}</div>
    </div>
  )
}

const NewChild = React.memo(Child)
export default NewChild

这时我们再点击MyFun组件中的更新按钮时,Child子组件就不会再跟着重新渲染了:

方法原理

首先查看memo方法源码:

js 复制代码
// react/src/ReactMemo.js

export function memo<Props>(
  type: React$ElementType,
  compare?: (oldProps: Props, newProps: Props) => boolean,
) {

  // 定义一个新的组件,包装传入的函数组件
  const elementType = {
    // 存储组件类型 memo
    $$typeof: REACT_MEMO_TYPE,
    type,
    # 一个cb回调函数,用于自定义校验props,根据校验结果决定是否需要重新渲染
    compare: compare === undefined ? null : compare,
  };

  // 返回包装后的组件
  return elementType;
}

从上面的源码可以看出,memo方法的内容比较简单,而且和forwardRef方法原理基本一致。它的方法内容就是定义一个新的内部组件,包装传入的函数组件,即将我们传递的函数组件存储到type属性上,然后返回包装后的组件即memo组件。

下面我们就开始解析React.memo方法创建的memo组件在加载和更新时具体的执行逻辑。

2,加载流程

首先查看memo组件的加载流程,因为memo组件首次加载会做一些比较重要的处理,我们需要了解。

开始执行memo组件对应Fiber节点的beginWork工作:

js 复制代码
// react-reconciler/src/ReactFiberBeginWork.new.js

function beginWork() {
    
	...
	// 根据fiber.tag,进入不同组件处理逻辑
	switch (workInProgress.tag) {
		case FunctionComponent: {}
		case ClassComponent: {}
		case HostRoot: {}
		case HostComponent: {}
        ...
        // 普通memo组件处理 14
		case MemoComponent: {
      		const type = workInProgress.type;
      		const unresolvedProps = workInProgress.pendingProps;
      		let resolvedProps = resolveDefaultProps(type, unresolvedProps);
            // 进入加载流程
        	return updateMemoComponent()
		}
        // simpleMemo组件处理 15
        case SimpleMemoComponent: {
            return updateSimpleMemoComponent()
        }
	}
}

首次加载时,该Fiber节点的tag属性值为14,直接进入MemoComponent分支处理逻辑:

js 复制代码
case MemoComponent: {
  // 组件type原型
  const type = workInProgress.type;
  // 等待处理的props
  const unresolvedProps = workInProgress.pendingProps;
  // 处理props默认值,两次
  let resolvedProps = resolveDefaultProps(type, unresolvedProps);
  resolvedProps = resolveDefaultProps(type.type, resolvedProps);
  return updateMemoComponent(
    current,
    workInProgress,
    type, // memo组件对象
    resolvedProps,
    renderLanes,
  );
}

这里取出Fiber节点的type属性内容,也就是memo组件对象的内容:

然后就是设置props的默认值:

js 复制代码
let resolvedProps = resolveDefaultProps(type, unresolvedProps);
resolvedProps = resolveDefaultProps(type.type, resolvedProps);

这里做了两次默认值处理:

  • 第一次是对memo组件的处理。
  • 第二次是对包裹的Child组件默认值处理。

最后调用一个updateMemoComponent方法,开始memo组件的加载处理。

updateMemoComponent

查看updateMemoComponent方法:

js 复制代码
// react-reconciler/src/ReactFiberBeginWork.new.js

function updateMemoComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any, // Fiber.type
  nextProps: any,
  renderLanes: Lanes,
): null | Fiber {
      
  # 加载阶段
  if (current === null) {
    const type = Component.type;
    if (
      isSimpleFunctionComponent(type) &&
      Component.compare === null &&
      Component.defaultProps === undefined
    ) {
      let resolvedType = type;
      // 1,SimpleMemo组件加载
      // 【重点】设置为SimpleMemo组件的tag,更新时就会走updateSimpleMemoComponent分支
      workInProgress.tag = SimpleMemoComponent; 
      workInProgress.type = resolvedType;
      return updateSimpleMemoComponent(
        current,
        workInProgress,
        resolvedType,
        nextProps,
        renderLanes,
      );
    }

    // 2,普通memo组件加载,直接创建子节点
    const child = createFiberFromTypeAndProps(
      Component.type,
      null,
      nextProps,
      workInProgress,
      workInProgress.mode,
      renderLanes,
    );
    child.ref = workInProgress.ref;
    child.return = workInProgress;
    workInProgress.child = child;
    return child;
  }

  # 更新阶段
  const currentChild = ((current.child: any): Fiber); // This is always exactly one child
  const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
    current,
    renderLanes,
  );
  if (!hasScheduledUpdateOrContext) {
    // This will be the props with resolved defaultProps,
    // unlike current.memoizedProps which will be the unresolved ones.
    const prevProps = currentChild.memoizedProps;
    // Default to shallow comparison
    let compare = Component.compare;
    compare = compare !== null ? compare : shallowEqual;
    if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
      // 跳过更新,返回原来的节点
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    }
  }
  // React DevTools reads this flag.
  // 创建新的节点
  workInProgress.flags |= PerformedWork;
  const newChild = createWorkInProgress(currentChild, nextProps);
  newChild.ref = workInProgress.ref;
  newChild.return = workInProgress;
  workInProgress.child = newChild;
  return newChild;
}

首先通过判断current节点是否为null来区分加载阶段和更新阶段。当前为加载阶段currentnull,继续查看memo组件加载阶段的逻辑处理。

js 复制代码
const type = Component.type;

注意: 这里的Componentmemo组件对象,可以查看上面的调试截图,取出的type才是Child组件本身。 在取出Child组件函数后,又通过一个条件判断,将memo组件的加载分成了两个分支逻辑:

  • simpleMemo组件加载。
  • 普通memo组件加载。
js 复制代码
if (isSimpleFunctionComponent(type) && Component.compare === null && Component.defaultProps === undefined) {
	// 1,simpleMemo组件加载
}

// 2,普通memo组件加载

同时满足上面三个判断条件的就是simpleMemo组件。

首先看第一个判断条件isSimpleFunctionComponent(type):是否为简单函数组件。

js 复制代码
// 判断是否为普通函数组件 forwardRef组件是对象:{$$typeof: Symbol(react.forward_ref)}
export function isSimpleFunctionComponent(type: any) {
  return (
    typeof type === 'function' && // 排除forwardRef组件
    !shouldConstruct(type) && // 排除类组件
    type.defaultProps === undefined // 无defaultProps
  );
}

注意: 这个方法的作用其实就是判断我们优化的这个组件:是否为简单函数组件

  • 第一个条件是排除forwardRef组件【对象】,因为memo组件也可以接收forwardRef组件。
  • 第二个条件是排除类组件,不能存在构造器。
  • 第三个条件是不存在defaultProps

只有同时满足这三个条件,才表示是简单函数组件simpleFunctionComponent

再看simpleMemo组件剩下的判断条件:即memo组件没有compare比较函数且自身也不存在defaultProps。同时满足这三个条件即可以认定为simpleMemo组件。

组件区分

到这里我们来进行一个总结,以此区分simpleMemo组件和普通memo组件:

simpleMemo组件:

  • React.memo(Com, null)方法组件参数为简单函数组件,且memo组件自身无compare参数,无defaultProps

memo组件:不满足simpleMemo组件条件的就是普通memo组件,比如:

  • React.memo(Com, compare)方法组件参数为简单函数组件,但memo组件自身存在compare参数或者defaultProps
  • React.memo(Com, compare)方法组件参数为forwardRef组件,此时不管存不存在compare参数或者defaultProps,它都是普通memo组件。
memo组件加载

如果是普通memo组件:就是继续创建子节点内容,最后返回子节点。

js 复制代码
// 普通memo组件加载
const child = createFiberFromTypeAndProps(
  Component.type,
  null,
   nextProps,
  workInProgress,
  workInProgress.mode,
  renderLanes,
);
child.ref = workInProgress.ref;
child.return = workInProgress;
workInProgress.child = child;
// Child组件 Fiber节点
return child;

通过普通memo组件的加载可以得知一个结论:普通memo组件会为包装的组件创建独立的的Fiber节点。

simpleMemo组件加载

这里我们的重点是查看simpleMemo组件的处理,因为当前我们传入的child满足simpleMemo组件的所有条件:

js 复制代码
if (isSimpleFunctionComponent(type) && Component.compare === null && Component.defaultProps === undefined) {
	# simpleMemo组件加载
    
    let resolvedType = type;
    // 【重点】设置为简单memo组件的tag,更新时就会走updateSimpleMemoComponent分支
    workInProgress.tag = SimpleMemoComponent; 
    workInProgress.type = resolvedType;
    // 简单函数组件,没有defaultProps
    return updateSimpleMemoComponent(
      current,
      workInProgress,
      resolvedType,
      nextProps,
      renderLanes,
    );
}

注意: 在判定为simpleMemo组件之后,将当前Fiber节点的tag属性设置为了SimpleMemoComponent对应的值,作用是此节点在更新阶段就可以直接进入updateSimpleMemoComponent分支逻辑处理,并且这里重置了Fiber节点的type属性,原来是memo组件对象,现在变成了Child组件函数【这样simpleMemo组件在加载阶段就和普通函数组件一致了】,查看调试截图印证:

最后调用updateSimpleMemoComponent方法,开始simpleMemo组件的加载。

updateSimpleMemoComponent

查看updateSimpleMemoComponent方法:

js 复制代码
// react-reconciler/src/ReactFiberBeginWork.new.js

function updateSimpleMemoComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderLanes: Lanes,
): null | Fiber {

  # 更新阶段
  if (current !== null) {
    const prevProps = current.memoizedProps;
    // 普通函数组件基本满足,props无变化,ref为null
    if (
      shallowEqual(prevProps, nextProps) &&
      current.ref === workInProgress.ref &&
      // Prevent bailout if the implementation changed due to hot reload.
      (__DEV__ ? workInProgress.type === current.type : true)
    ) {
      didReceiveUpdate = false;
      workInProgress.pendingProps = nextProps = prevProps;

      if (!checkScheduledUpdateOrContext(current, renderLanes)) {
        workInProgress.lanes = current.lanes;
        // 复用原来的节点,跳过更新
        return bailoutOnAlreadyFinishedWork(
          current,
          workInProgress,
          renderLanes,
        );
      } else if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
        didReceiveUpdate = true;
      }
    }
  }
      
  # 加载阶段:其实就是普通函数组件加载逻辑 
  // 更新阶段:不满足上面的优化条件时,也会进入普通函数组件的更新流程
  return updateFunctionComponent(
    current,
    workInProgress,
    Component,
    nextProps,
    renderLanes,
  );
}

此方法中同样是通过判断current节点是否有值来区分加载阶段和更新,当前为加载阶段最后直接调用了一个updateFunctionComponent方法,也就是说simpleMemo组件的加载逻辑实际上调用的普通函数组件的加载逻辑。

3,更新流程

触发更新

点击MyFun组件的更新按钮,触发一次更新流程。

下面我们直接快进来到MyFun组件的更新逻辑:

MyFun组件在调用renderWithHooks方法之后,会根据组件返回的jsx内容重新创建所有子节点对应的react元素对象。

其中就包含了memo组件对应的react元素对象:

这里react元素对象的type属性存储的就是上面memo方法里面的elementType对象,而它内部的type属性才是原始的child组件。

下面我们继续快进,直接来到memo组件的更新流程,开始执行memo组件对应Fiber节点的beginWork工作:

js 复制代码
// react-reconciler/src/ReactFiberBeginWork.new.js

function beginWork() {
    
    if (current !== null) {
      // 更新逻辑
      const oldProps = current.memoizedProps;
      const newProps = workInProgress.pendingProps;
      // 主要判断:props变化和context上下文变化
      if (oldProps !== newProps || hasLegacyContextChanged() ) {
        // props有变化或者上下文有变化时,Fiber节点自身需要更新就会进入这里
        didReceiveUpdate = true;
      } else {
        // 无变化,进入Bailout策略
          ...
      }
    } else {
        // 加载逻辑
        didReceiveUpdate = false;
    }
	...
	// 根据fiber.tag,进入不同组件处理逻辑
	switch (workInProgress.tag) {
		case FunctionComponent: {}
		case ClassComponent: {}
		case HostRoot: {}
		case HostComponent: {}
        ...
        // 普通memo组件处理 14
		case MemoComponent: {
      		const type = workInProgress.type;
      		const unresolvedProps = workInProgress.pendingProps;
      		let resolvedProps = resolveDefaultProps(type, unresolvedProps);
            // 进入加载流程
        	return updateMemoComponent()
		}
        // simpleMemo组件处理 15
        case SimpleMemoComponent: {
            return updateSimpleMemoComponent()
        }
	}
}

这里memo组件节点的pendingProps属性接收的新建的react元素对象传递的props数据,所以这里的新旧props不相等,不满足Bailout优化策略,会执行正常的更新流程。

simpleMemo组件更新

当前我们为simpleMemo组件,它的tag15【前面加载阶段中更新的tag属性】,所以这里更新时会直接进入SimpleMemoComponent分支逻辑,下面我们继续查看它的更新逻辑。

直接进入updateSimpleMemoComponent方法的更新逻辑:

js 复制代码
// react-reconciler/src/ReactFiberBeginWork.new.js

function updateSimpleMemoComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderLanes: Lanes,
): null | Fiber {

  # 更新阶段
  if (current !== null) {
    const prevProps = current.memoizedProps;
    // 普通函数组件基本满足,props无变化,ref为null
    if (shallowEqual(prevProps, nextProps) && current.ref === workInProgress.ref) {
      didReceiveUpdate = false;
      workInProgress.pendingProps = nextProps = prevProps;

      if (!checkScheduledUpdateOrContext(current, renderLanes)) {
        workInProgress.lanes = current.lanes;
        // 复用原来的节点,跳过更新
        return bailoutOnAlreadyFinishedWork(
          current,
          workInProgress,
          renderLanes,
        );
      }
    }
  }
      
  // 加载阶段
  // 或者不满足优化策略条件时,进入普通函数组件更新流程
  return updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes)
}

注意: 这里判断current时和下面的updateFunctionComponent并不是if else,即不是二选一的逻辑。在当前更新流程中,如果不满足Bailout优化策略条件,则还是会进入下面的updateFunctionComponent方法,执行普通函数组件的更新流程。

所以memo方法优化函数组件的原理就是:在函数组件更新之前,再执行一次Bailout策略校验。

我们继续查看simpleMemo组件的优化逻辑,首先从current节点上取出旧的props对象:

js 复制代码
const prevProps = current.memoizedProps;

然后调用默认的shallowEqual方法对新旧props对象进行浅比较,判断props是否发生了变化:

js 复制代码
if (shallowEqual(prevProps, nextProps) && current.ref === workInProgress.ref)

这里我们接着查看shallowEqual浅比较方法:

shallowEqual
js 复制代码
// packages\shared\shallowEqual.js

import is from './objectIs';
import hasOwnProperty from './hasOwnProperty';
// 浅比较
export default function shallowEqual(objA: mixed, objB: mixed): boolean {
  // objA:旧的props;  objB:新的props
  if (is(objA, objB)) {
    return true;
  }

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

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

  if (keysA.length !== keysB.length) {
    return false;
  }

  // Test for A's keys different from B.
  for (let i = 0; i < keysA.length; i++) {
    const currentKey = keysA[i];
    if (
      !hasOwnProperty.call(objB, currentKey) ||
      !is(objA[currentKey], objB[currentKey])
    ) {
      return false;
    }
  }

  return true;
}

一进入shallowEqual方法,首先就使用Object.is()来判断新旧props对象是否相等:

注意: 实际情况中组件更新时,新旧props对象都不会相等,以当前案例中的MyFun组件为例,每次当该组件更新时,它都会重新创建组件内所有的子节点的react元素对象【其中就包括newChild组件】,在创建react元素对象时,每次都是新建的一个props对象,所以即使你的props没有任何数据为空对象,它也不会和旧的props对象相等。

yaml 复制代码
{} !== {}

Child组件Fiber节点的props对象就是取自于它对应的react元素对象,所以这里第一个判断条件无法满足。

shallowEqual方法剩下判断逻辑就比较简单了:通过Object.keys方法得到新旧props对象的keys,然后循环旧props对象的keys,判断新props对象是否存在对应的属性以及属性值是否相等:

  • 如果有任何一个属性判断不通过,则返回false,表示props发生变化,需要执行普通函数组件的更新流程。
  • 如果所有判断条件都通过,则返回true,表示props没有发生变化,可以执行进一步的优化策略。
js 复制代码
if (shallowEqual(prevProps, nextProps) && current.ref === workInProgress.ref)

最后还要注意:这里除了判断props之外,还判断了ref对象的变化。

这也就是为何函数组件要使用useRef来创建ref对象的原因,以此固定ref对象的引用地址,这样我们在使用React.memo方法优化函数组件时,才能够满足这个判断条件,以此进入Bailout优化策略。

当前我们的simpleMemo组件满足判断条件:props对象无属性变化,没有使用ref,所以ref对象都为null

然后设置全局变量didReceiveUpdatefalse,表示不需要更新。

ini 复制代码
didReceiveUpdate = false;

在通过上面的判断条件通过后,还需要继续判断是否存在更新。在第一章节已经讲述过,影响Fiber节点更新有三个因素:propsstatecontext,当前仅仅是通过了props的判断,还需要执行进一步的优化条件判断:

js 复制代码
// 进一步的优化条件判断
if (!checkScheduledUpdateOrContext(current, renderLanes)) {
  workInProgress.lanes = current.lanes;
  // 优化策略工作
  return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}

调用checkScheduledUpdateOrContext方法来判断statecontext变化影响的更新,这里的判断逻辑和前面的beginWork中的判断逻辑完全一致,因为它们本来就是相同的Bailout策略校验,两个不同的地方触发的校验,唯一的区别就是对props的校验有所不同,所以这里就直接快速说明了,详细的逻辑可以查看《React函数组件性能优化三部曲(一)》。

检查当前Fiber节点是否存在等待处理的更新:

  • 如果存在:则返回true,取反后为false,不满足Bailout策略,进入普通的函数组件更新流程。
  • 如果不存在:则返回false,取反后为true,满足Bailout策略,进入组件优化策略流程。

当上面的校验都通过后,进入bailoutOnAlreadyFinishedWork方法,执行进一步的优化程度判断。

bailoutOnAlreadyFinishedWork

查看bailoutOnAlreadyFinishedWork方法:

js 复制代码
// packages\react-reconciler\src\ReactFiberBeginWork.new.js

function bailoutOnAlreadyFinishedWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  if (current !== null) {
    workInProgress.dependencies = current.dependencies;
  }
 // 标记跳过更新的lanes
  markSkippedUpdateLanes(workInProgress.lanes);

  // 检查当前节点的子树是否存在更新的工作
  if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
	return null;
  }

  // 当前Fiber节点没有更新工作,但是它的子树有更新工作,克隆下一层子节点,继续执行子节点的beginWork工作
  cloneChildFibers(current, workInProgress);
  return workInProgress.child;
}

这个方法是Bailout优化策略的核心方法,它的作用执行进一步的优化程度判断:

  • 高度优化:跳过整个子树的更新流程。
  • 一般优化:跳过自身节点的更新流程。

当前我们的simpleMemo组件【Child】满足高度优化策略,就会直接跳过该组件所有节点的更新流程。

这就是React.memo方法对函数组件的优化作用,它的实际原理就是在beginWork之后,组件更新之前,再执行一次Bailout策略校验。

到此simpleMemo组件的更新逻辑就执行完成。

memo组件更新

下面我们再来查看一下普通memo组件的更新流程,此时我们需要对之前案例进行改造,最简单的就是传递forwardRef组件。

js 复制代码
// MyFun.js
import { useState, useRef } from 'react'
import NewChild from './NewChild'

export default function MyFun(props) {
  console.log('MyFun组件运行了')
  const [count, setCount] = useState(1)
  const ref = useRef()
  function handleClick() {
    setCount(count + 1)
  }
  return (
    <div className='MyFun'>
      <div>state: {count}</div>
      <NewChild name='NewChild' ref={ref}></NewChild>
      <button onClick={handleClick}>更新</button>
    </div>
  )
}
js 复制代码
// NewChild.js
import React from 'react'

function Child(props, ref) {
  console.log('Child组件运行了')
  return (
    <div className='Child'>
      <div ref={ref}>Child子组件</div>
      <div>name: {props.name}</div>
    </div>
  )
}

const NewChild = React.memo(React.forwardRef(Child))
export default NewChild

下面我们直接来到它的更新流程:

js 复制代码
// react-reconciler/src/ReactFiberBeginWork.new.js

function updateMemoComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderLanes: Lanes,
): null | Fiber {
      
  # 加载阶段
  if (current === null) {
	...
  }

  # 更新阶段
  const currentChild = current.child; // This is always exactly one child
  const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
    current,
    renderLanes,
  );
  if (!hasScheduledUpdateOrContext) {
    const prevProps = currentChild.memoizedProps;
    // Default to shallow comparison
    let compare = Component.compare;
    compare = compare !== null ? compare : shallowEqual;
    if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
      // 跳过更新,返回原来的节点
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    }
  }

  // 不满足bailout策略时
  // 根据旧的节点内容及新的props创建新的Fiber节点
  const newChild = createWorkInProgress(currentChild, nextProps);
  newChild.ref = workInProgress.ref;
  newChild.return = workInProgress;
  workInProgress.child = newChild;
  return newChild;
}

这里我们可以看出普通memo组件和simpleMemo组件更新时是基本一致的,也是执行一次Bailout策略校验:

  • 校验通过,则进入Bailout策略逻辑。
  • 校验不通过,则直接复用旧节点信息以及新的props生成新的Fiber节点,然后执行组件正常的更新逻辑。

这里和simpleMemo组件更新不同的地方在于,如果传递了自定义compare比较函数,则调用此方法,如果没有传递这个参数,则会使用默认的shallowEqual方法进行props的浅比较。

4,总结

1,当我们使用React.memo(Com, null)方法来优化我们的函数组件时,会根据传递的参数不同,在react内部划分为两种组件类型:

  • simpleMemo组件
  • 普通memo组件

2,这两个组件具体的区分条件为:

simpleMemo组件:

  • React.memo(Com, null)方法组件参数为简单函数组件,且memo组件自身无compare参数,无defaultProps

simpleFunctionComponent简单函数组件条件:必须为函数组件且自身无defaultProps

memo组件:不满足simpleMemo组件条件的就是普通memo组件,比如:

  • React.memo(Com, compare)方法组件参数为简单函数组件,但memo组件自身存在compare参数或者defaultProps
  • React.memo(Com, compare)方法组件参数为forwardRef组件,此时不管存不存在compare参数或者defaultProps,它都是普通memo组件。

3,这两个组件在更新阶段主要的区别就是对props校验的不同:

  • simpleMemo组件使用react内部默认的shallowEqual浅比较方法。
  • 普通memo组件可以使用用户传递的自定义的compare比较函数,如果没有传递则也是使用默认的shallowEqual方法。

当然这两个组件在不满足Bailout策略时,创建子节点的逻辑也些许不同,不过这个对我们来说不是重点。

4,simpleMemo组件和普通memo组件可以统称为memo组件:因为它们优化函数组件的原理是相同的,就是在该节点beginWork之后,函数组件更新之前,再执行一次Bailout策略校验,其实就是多给函数组件一次救赎的机会。

结束语

以上就是React.memo方法优化函数组件的全部内容了,觉得有用的可以点赞收藏!如果有问题或建议,欢迎留言讨论!

相关推荐
鹧鸪yy1 分钟前
认识Node.js及其与 Nginx 前端项目区别
前端·nginx·node.js
跟橙姐学代码2 分钟前
学Python必须迈过的一道坎:类和对象到底是什么鬼?
前端·python
汪子熙4 分钟前
浏览器里出现 .angular/cache/19.2.6/abap_test/vite/deps 路径究竟说明了什么
前端·javascript·面试
Benzenene!5 分钟前
让Chrome信任自签名证书
前端·chrome
yangholmes88885 分钟前
如何在 web 应用中使用 GDAL (二)
前端·webassembly
jacy7 分钟前
图片大图预览就该这样做
前端
林太白9 分钟前
Nuxt3 功能篇
前端·javascript·后端
YuJie11 分钟前
webSocket Manager
前端·javascript
Mapmost26 分钟前
Mapmost SDK for UE5 内核升级,三维场景渲染效果飙升!
前端
Mapmost28 分钟前
重磅升级丨Mapmost全面兼容3DTiles 1.1,3DGS量测精度跃升至亚米级!
前端·vue.js·three.js