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方法优化函数组件的全部内容了,觉得有用的可以点赞收藏!如果有问题或建议,欢迎留言讨论!

相关推荐
奋斗吧程序媛3 分钟前
删除VSCode上 origin/分支名,但GitLab上实际上不存在的分支
前端·vscode
IT女孩儿13 分钟前
JavaScript--WebAPI查缺补漏(二)
开发语言·前端·javascript·html·ecmascript
m0_748256562 小时前
如何解决前端发送数据到后端为空的问题
前端
请叫我飞哥@2 小时前
HTML5适配手机
前端·html·html5
@解忧杂货铺4 小时前
前端vue如何实现数字框中通过鼠标滚轮上下滚动增减数字
前端·javascript·vue.js
F-2H6 小时前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++
gqkmiss6 小时前
Chrome 浏览器插件获取网页 iframe 中的 window 对象
前端·chrome·iframe·postmessage·chrome 插件
m0_748247559 小时前
Web 应用项目开发全流程解析与实战经验分享
开发语言·前端·php
m0_748255029 小时前
前端常用算法集合
前端·算法
真的很上进9 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html