文章

memo

memo

memo

memo

memo 是个高阶组件, 结合了 PurComponentshouldComponentUpdate 功能,会对传入的 props 进行**浅比较**,来决定是否更新被包裹的组件

memo 接受两个参数:

  • WrapComponent:你要优化的组件
  • (prev, next) => boolean:通过对比 prev(旧 props),next(新 props)是否一致,返回 true(不更新)、false(更新)

注意:memo 只针对 props 来决定是否渲染,且是**浅比较**


现在我们来看一个的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Button } from "antd";
import React, { useState } from "react";

const Child = () => <div>{console.log("子组件又渲染")}</div>;

const Parent = () => {
  const [number, setNumber] = useState(0);
  const [flag, setFlag] = useState(false);

  return (
    <>
      <Child />
      <Button type='primary' onClick={() => setFlag(!flag)}>
        {flag ? "显示" : "隐藏"}
      </Button>
    </>
  );
};

export default Parent;

运行结果如下:

1697080059363-76bd4042-760e-499f-886e-97f5bf8133e0.png

在上面的例子中,父组件中的两个状态 numberflag 都和 Child 组件没有关系,当我点击按钮时,flag 发生改变,此时父组件重新渲染,按钮文案变为显示,控制台却打印出 “子组件又渲染” 的信息,说明子组件也跟着重新渲染了。而这肯定是不合理的,我们不希望子组件做无关的刷新,此时我们可以给子组件加上 React.memo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Button } from "antd";
import React, { useState } from "react";

const Child = React.memo(() => <div>{console.log("子组件又渲染")}</div>);

const Parent = () => {
  const [number, setNumber] = useState(0);
  const [flag, setFlag] = useState(false);

  return (
    <>
      <Child />
      <Button type='primary' onClick={() => setFlag(!flag)}>
        {flag ? "显示" : "隐藏"}
      </Button>
    </>
  );
};

export default Parent;

运行结果如下:

1697080234521-4e9692d4-459c-4d15-aada-8ed9e813fc42.png

在给子组件加上 memo 包裹后,再次点击按钮,flag 发生改变,此时控制台并没有打印 “子组件又渲染” 的信息,说明此时子组件不会做无关的刷新,从而达到了性能优化的目的。

总而言之,如果组件被 ****memo** 包裹,那么组件的 **props** 不发生改变时,组件不会重新渲染。这样,我们合理的使用 **memo** 就可以为我们的项目带来很大的性能优化。**

memo 的注意事项

虽然,memo 可以帮助我们避免组件无意义的重新渲染,达到性能优化的目的,但是你还是得注意一下 memo 的一些注意事项

####

memo 对 props 是浅比较 上文我们也说了,memo 对于新旧 props 的比较是浅比较,当一个引用类型的 props 改变时,只要它的地址没有发生改变,那么就算 props 中某一项数据发生了改变,那么被 memo 包裹的组件是不会重新渲染的。

比如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { Button, Divider } from "antd";
import React, { useState } from "react";

const Child = React.memo((props) => (
  <div>
    {props.list.map((item) => (

      <div style={{marginLeft: '20px'}}> {item} </div>

    ))}
  </div>
));

const Parent = () => {
  const [list, setList] = useState([1, 2, 3]);

  return (
    <>
      <Child list={list} />
      <Divider />
      <Button
        type='primary'
        onClick={() => {
          list.push(4);
          console.log('list', list)
        }}
      >
        点击改变 list
      </Button>
    </>
  );
};

export default Parent;

上面的代码,当我们点击按钮后,运行结果如下

1697080783307-8102cd0a-0e33-413f-b42f-eb4380466487.png

我们可以看见,虽然点击了按钮改变了 list,但此时子组件渲染的只有初始的 1 2 3,并没有重新渲染,这是因为虽然 list 内容改变了,但是 list 是引用类型的数据,memo 对新旧 list 进行浅比较,发现地址没变,就不重新渲染。

那这种情况怎么办呢?很简单,返回一个新的数组即可

1
2
3
onClick={() => {
 setList([...list, 4])
}}

1697080863445-3081fab7-0d6b-422d-9bad-37429b6e264e.png

setList,子组件重新渲染

memo 是否用的越多越好

既然 memo 可以对组件进行性能优化,那能不能所有组件都用 memo 包裹呢?

答案肯定是否定的。

因为缓存本身也是需要开销的。如果每一个组件都用 memo 去包裹一下,那么对浏览器的开销就会很大,本末倒置了。

所以我们应该选择性的用 memo 包裹组件,而不是滥用。

在项目中,一般如果一个子组件经常被重新渲染,那需要根据具体情况具体分析,有目的性的去缓存它。

本文由作者按照 CC BY 4.0 进行授权