文章概叙
本文主要讲的是React中memo的使用,以及考虑是否使用memo的判断依据
memo介绍
memo 允许你的组件在 props 没有改变的情况下跳过重新渲染。
在使用memo将组件包装起来之后,我们可以获得该组件的一个 记忆化 版本。通常情况下,只要该组件的 props 没有改变,这个记忆化版本就不会在其父组件重新渲染时重新渲染。
也正是因为如此,在开发过程中,我们都会说“用memo来缓存组件,跳过组件的重复渲染”等说法,此时所说的memo就是该API,当我们传入的props没有变化(需要注意的是,数组等对象,引用地址不能变化),组件就不会被重新渲染了,就能适当的减少我们的开支~
简单示例
下面的例子中,父组件包括了一个循环的定时器,以及一个子组件,不过对子组件传入的是一个固定的值,代码如下
//父组件的代码
import React, { useEffect, useState } from "react";
import "./App.css";
import MemoComponent from "./components/MemoComponent";
function App() {
const [date, setDate] = useState(0);
useEffect(() => {
setInterval(() => {
setDate(+new Date());
}, 1000);
}, []);
return (
<div className="App">
<p>{date}</p>
<MemoComponent num_array={1} />
</div>
);
}
export default App;
//子组件的代码
export default (props: any) => {
console.log("子组件是否刷新");
return (
<>
<div>这个是其中一个子组件</div>
</>
);
};
而由于父组件的一直刷新,所以我们的子组件也会跟着被重新渲染。
此时,我们在考虑如何优化我们的组件时,方向就很明显了,就是当计时器一直刷新的时候,我们的子组件并不需要一直刷新。
那么这时候我们就可以使用memo来跳过子组件的重新渲染。
memo语法
memo(Component, arePropsEqual)
-
Component
我们需要进行记忆化的组件,react并不会对其做任何的修改,只是做一个高阶组件的处理,添加完自己的操作后返回给你。 -
arePropsEqual
可选参数,接受两个参数:组件的前一个 props 和新的 props。一般情况我们不需要去管他,直接忽略他,因为React会使用Object.js去判断前后props是否相同,如果我们手动设置了,会很容易出啥错。
使用memo
理解了memo的语法之后,我们可以在我们的子组件中,使用memo将组件包含起来,使其跳过重复渲染,代码如下
//子组件的代码
import { memo } from "react";
export default memo((props: any) => {
console.log("子组件是否刷新");
return (
<>
<div>这个是其中一个子组件</div>
</>
);
});
此时,由于我们使用memo,子组件避免了无用的重复渲染,所以控制台就不会一直显示子组件被渲染的情况了。
memo的地址判断
上章提及到,memo会判断我们传入的props是否一样,但当我们传入一个数组(或者对象)的时候,我们会发现我们的页面中,子组件又被重新渲染了。
//父组件代码
<MemoComponent num_array={[1,2,3,4]} />
这是因为,当我们使用数组或者是对象的时候,由于使用的Object.js在判断对象是否一致的时候,会判断引用地址,所以我们看起来值一样,但是实际上不是同一个(参考js数据类型)。
此时我们可以使用useState来保证数组不被重新赋值。
//父组件的代码
import React, { useEffect, useState } from "react";
import "./App.css";
import MemoComponent from "./components/MemoComponent";
function App() {
const [date, setDate] = useState(0);
const [numArray,setNumArray]=useState([1,2,3,4,5])
useEffect(() => {
setInterval(() => {
setDate(+new Date());
}, 1000);
}, []);
return (
<div className="App">
<p>{date}</p>
<MemoComponent numArray />
</div>
);
}
export default App;
使用了useState之后,由于我们的numArray的每次都是一样的值,一样的地址,子组件就不会被重新刷新了。
所以当你使用memo之后,发现你的项目并没有按照你的预想走的时候,可以检查下你的组件的props。
滥用memo
既然memo有那么多的好处,那为什么我们的项目中没有出现所有的组件都用memo包起来呢?
下面的例子中,我们使用了memo包括了10个组件,且在5s后,我们的父组件会刷新一次页面,而子组件的代码不变。
//父组件代码
import React, { useEffect, useState } from "react";
import "./App.css";
import MemoComponent from "./components/MemoComponent";
function App() {
const [date, setDate] = useState(0);
useEffect(() => {
setTimeout(() => {
setDate(+new Date());
}, 5000);
}, []);
return (
<div className="App">
<p>{date}</p>
{new Array(10).fill(1).map((v) => (
<MemoComponent />
))}
</div>
);
}
export default App;
下面是使用了memo处理组件的情况下,大概理解为当计时器触发之后,我们的页面花了14ms来重新渲染我们的页面(3915-3901)
但是当我们去掉了代码中的memo之后,我们发现,计时器触发之后,我们的页面只需要花费11ms(3388-3377)的时间来渲染。
你以为我会跟借此跟你说,当给每一个组件都添加了memo之后,由于props的判断会导致页面渲染更多时间吗?
官网上提及到下面这段话
只有当你的组件经常使用完全相同的 props 重新渲染时,并且其重新渲染逻辑是非常昂贵的,使用 memo
优化才有价值。如果你的组件重新渲染时没有明显的延迟,那么 memo 就不必要了。请记住,如果传递给组件的 props
始终不同,例如在渲染期间传递对象或普通函数,则 memo 是完全无用的。这就是为什么你通常需要在 memo 中同时使用 useMemo 和
useCallback
众所周知,我们的react是通过判断props是否发生变化的来判定是否重新渲染组件的,那么我们可以理解,当我们用一个很复杂的props来节省一个很简单的子组件的时候,我们是否就已经失败了呢?
因为我们的react需要先去判断props是否相同,而当花费了30ms的时间在判断props后,结果只是缓存了一行文字…你猜react会不会气抖冷?
毕竟你去吃饭,老板弄了一道高数题,说解出来了,答案就是wifi密码,你千辛万苦解出来,发现老板家的wifi只有1k/s,不单单打扰你跟小姐姐聊天,还用你手机挖矿,你就知道什么感觉了。
至于React的memo是否会占用多一些空间缓存的,由于本人没有具体的demo,所以推荐大家看下github上的答复,就不验证了
一个前端博客,希望能帮到小白们
公众号求关注~