React.memo 的用途

优化 React

优化函数组件的性能

React.memo 是 React 库中的一个高阶组件,旨在优化函数组件的性能。当一个函数组件在相同的 props 下反复渲染时,React.memo 允许 React 跳过渲染步骤并直接复用最后一次渲染的结果。

工作原理

当一个包裹在 React.memo 中的组件接收到新的 props 时,React 将执行一个浅层比较来判断是否需要重新渲染。如果 props 没有变化,组件则不会重新渲染,从而减少不必要的计算和 DOM 操作。

语法

javascript
1const MemoizedComponent = React.memo(MyComponent);

参数

  • MyComponent:待优化的函数组件。
  • areEqual(可选):一个用于自定义比较函数的参数,接收两个参数,即新旧 props,返回一个布尔值。

应用场景

  1. 性能优化:当一个组件在相同的 props 下反复渲染时。
  2. 避免副作用:当重新渲染可能触发不必要的副作用或计算时。

限制和注意事项

  1. 只用于函数组件React.memo 是为函数组件设计的,不应用于 class 组件。
  2. 浅层比较:默认情况下,比较是浅层的,可能不适用于深层嵌套的对象。
  3. 副作用与复杂性:过度使用 React.memo 可能会引入额外的复杂性并影响代码可读性。

示例

javascript
1const MyComponent = ({ name }) => {
2  console.log('Rendered:', name);
3  return <div>{name}</div>;
4};
5
6const areEqual = (prevProps, nextProps) => {
7  return prevProps.name === nextProps.name;
8};
9
10const MemoizedMyComponent = React.memo(MyComponent, areEqual);

总体而言,React.memo 是一个用于优化 React 函数组件性能的有用工具,但需要谨慎使用,以避免不必要的复杂性和潜在问题。

性能提升的误区

根据经验,对 props 对象进行浅层比较总是比调用 React.createElement() 和比较整个组件树要便宜。

但是,如果你的组件有深度比较的 props,那么 React.memo 的浅比较是不够的。此外,如果你的组件接收并显示 children 属性值,则此高阶组件不起作用。

如下代码:

ts
1import type { FC, PropsWithChildren } from 'react';
2import { memo, useEffect, useState } from 'react';
3
4const _Child: FC<PropsWithChildren> = ({ children }) => {
5  console.log('Child');
6  return <div>{children}</div>;
7};
8
9const Child = memo(_Child);
10
11export const Test = () => {
12  const [num, setNum] = useState(0);
13  console.log(num);
14
15  useEffect(() => {
16    console.log('useEffect');
17    setNum(1);
18  }, []);
19
20  return (
21    <div>
22      <Child>
23        <div>Child</div>
24      </Child>
25    </div>
26  );
27};

严格模式下输出如下:

Test.tsx:13 0 Test.tsx:13 0 Test.tsx:5 Child Test.tsx:5 Child Test.tsx:16 useEffect Test.tsx:16 useEffect Test.tsx:13 1 Test.tsx:13 1 Test.tsx:5 Child Test.tsx:5 Child

当接受一个非 primitive 的 children(当然不仅是 children, children 也只是一个普通的 prop。) 时,memo 不能起到任何作用反而增加一次浅比较和无效缓存。使用 useMemo 包裹 <div>Child</div> 可以避免额外渲染,但是额外增加了代码复杂度。

内存消耗

由于 React.memo 通过缓存组件实例来避免不必要的重新渲染,这将消耗额外的内存。在某些情况下,这可能会成为问题。

正确姿势

为了正确地使用 React.memo 并最大化其潜在优势,建议遵循以下准则:

1. 性能诊断

在使用 React.memo 之前,首先应使用性能诊断工具(如 React DevTools)来确认组件是否确实存在不必要的渲染。

2. 简单组件优先

适用于那些渲染输出完全由 props 确定,并且没有副作用或依赖于全局状态的组件。

3. 明确目标

明确优化目标,如减少渲染次数、减少计算量等,以便更有针对性地应用 React.memo

4. 自定义比较函数

如果默认的浅比较不符合需求,可以传递一个自定义比较函数给 React.memo

javascript
1const areEqual = (prevProps, nextProps) => {
2  // 自定义比较逻辑
3  return prevProps.id === nextProps.id;
4};
5
6const MemoizedComponent = React.memo(MyComponent, areEqual);

5. 避免内联对象和函数

避免在渲染过程中创建内联对象和函数,因为这将导致 React.memo 的浅比较失败。

javascript
1// 避免这种做法
2<MemoizedComponent style={{ color: 'blue' }} onClick={() => doSomething()} />

6. 与其他优化手段配合

考虑 React.memo 与其他性能优化手段(如 useMemo, useCallback, PureComponent 等)的组合使用。

7. 测试与审查

在实施任何优化后,都应进行全面的功能和性能测试,确保没有引入新的问题或副作用。

8. 文档和代码注释

清晰地记录为何使用 React.memo,以及你期望达到什么效果,这样能帮助团队其他成员理解其用途和限制。

通过遵循上述准则,你可以更加有效地利用 React.memo 进行性能优化,同时避免或最小化潜在的问题和副作用。

总而言之,越是简单越是纯粹的组件更适合 React.memo