前言
在之前的一篇文章React 函数式组件优化中,提到可以利用 useCallback
来将函数记忆化,以达到每次传给子组件的都是同一个函数,避免子组件函数重复执行,提升性能。
针对函数的记忆化,除了使用 useCallback
之外,还能使用 useMemo
。
useMemo
useMemo
的用法与 useCallback
类似,但 useCallback
是直接传入需要记忆化的函数,而 useMemo
是传入一个函数,这个传入的函数返回你需要记忆化的函数,示例如下:
const childOnClick = React.useMemo(() => {
return () => {
console.log(`m: ${m}`);
};
}, [m]);
完整代码点这里,效果如下:
乍一看两者实现的功能都一样,用 useMemo
相比 useCallback
还需要多写一层函数,那 useMemo
相比 useCallback
有什么优势呢?
数据记忆化
- 对象记忆化
在实际的工作中,我们还会遇到一种情况,就是传递给子组件的不是一个函数,而是一个对象数据,如果对象数据没做记忆化处理,那么即使对象的具体数据没变,但每次都是一个新地址的对象,也会被认为是一个新对象,引发子组件函数重新执行。
import * as React from "react";
import { render } from "react-dom";
import "./styles.css";
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
const data = { m: m };
return (
<div className="App">
<div>React.useMemo demo-3</div>
<button
onClick={() => {
setN(n + 1);
}}
>
update n: {n}
</button>
<button
onClick={() => {
setM(m + 1);
}}
>
update m: {m}
</button>
<Child data={data} />
</div>
);
}
const Child = React.memo((props: { data: { m: number } }) => {
console.log("render child");
return (
<div className="child">
<div>m:{props.data.m}</div>
<div>Child Component</div>
</div>
);
});
const rootElement = document.getElementById("root");
render(<App />, rootElement);
也可以点击这里查看,具体效果如下:
而 useCallback
无法记忆化对象数据,那这时候就可以利用 useMemo
对数据进行记忆化。
const data = React.useMemo(() => {
return { m: m };
}, [m]);
完整代码点这里,具体效果如下:
- 计算结果记忆化
useMemo
除了可以对对象进行记忆化,还可以对基本类型的数据进行记忆化,比如对需要进行大量计算才能得到的结果进行记忆化,避免组件更新时进行重复的大量计算。
看一个例子。
import * as React from "react";
import { render } from "react-dom";
import "./styles.css";
function App() {
const [n, setN] = React.useState(1);
const [m, setM] = React.useState(0);
const factorial = React.useMemo(() => {
console.log("计算了一次阶乘");
let result = 1;
for (let i = 1; i <= n; i++) {
result = result * i;
}
return result;
}, [n]);
return (
<div className="App">
<div>React.useMemo demo-4</div>
<div>
{n}的阶乘是:{factorial}
</div>
<button
onClick={() => {
setN(n + 1);
}}
>
update n: {n}
</button>
<button
onClick={() => {
setM(m + 1);
}}
>
update m: {m}
</button>
</div>
);
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
完整代码点这里,效果如下:
可以看到,useMemo
的依赖数组只包含 n
,当 m
更新时,直接返回记忆化的值,所以阶乘就没有再次计算;而更新了 n
时,之前记忆的数字就需要再次更新,这时才再次计算阶乘。
总结
利用 useMemo
我们不仅可以记忆化函数,还可以记忆化数值,避免函数重复计算,提升性能。
需要注意的是我们使用这些 Hooks
来进行优化时,本身也是会消耗性能的,如果优化过程消耗的性能大于优化节约的性能,那就得不偿失了。