在 React 中,可以使用useMemo 和useCallback 来优化无用渲染的性能问题。本文是自己学习useMemo 和useCallback 的笔记,介绍了它们的使用场景和区别。
1. useMemo
useMemo 用来缓存值,保存每次渲染时,不会发生变化的值,减少计算。在一些开销较大且值不会变的场景下特别有用。
如下代码所示,定义了两个状态max 和count ,在页面上显示当前max 和count 的值,并可以点击按钮增加max 和count 的值。
定义了一个函数getSum 求0 ~max 的和,模拟一个开销大的操作,将求和的结果显示在页面上。但是这个求和的操作只依赖于max 这个state ,与其他值无关。
import { useState } from 'react';
const A = () => {
const [max, setMax] = useState(100);
const [count, setCount] = useState(0);
const getSum = () => {
console.log('sum被重新计算了');
let sum = 0;
for (let i = 0; i <= max; i++) {
sum += i;
}
return sum;
};
return (
<>
<div>sum:{getSum()}</div>
<div>
max:{max} <button onClick={() => setMax(max => max + 1)}>max++</button>
</div>
<div>
count:{count}
<button onClick={() => setCount(count => count + 1)}>count++</button>
</div>
</>
);
};
export default A;
更改count 的值,求和结果sum 并不会发生变化,因为求和的操作只依赖于max 。若不使用useMemo 进行优化,如下图所示,每次更改count 的值,都会重新调用一次getSum 函数,每次都进行了一次不必要的操作。
可以使用useMemo 进行优化,如下代码所示,使用useMemo 将函数包裹起来,并在useMemo 的第二个参数中,写上max 这个依赖项,表示只有当max 发生变化时,才重新调用一次getSum 函数,否则就使用上一次计算的值。
import { useState, useMemo } from 'react';
const A = () => {
const [max, setMax] = useState(100);
const [count, setCount] = useState(0);
const getSum = useMemo(() => {
console.log('sum被重新计算了');
let sum = 0;
for (let i = 0; i <= max; i++) {
sum += i;
}
return sum;
}, [max]);
return (
<>
{}
<div>sum:{getSum}</div>
<div>
max:{max} <button onClick={() => setMax(max => max + 1)}>max++</button>
</div>
<div>
count:{count}
<button onClick={() => setCount(count => count + 1)}>count++</button>
</div>
</>
);
};
export default A;
结果如下图所示,更新count 时,并不会重新调用getSum 。只有max 变化时,才会重新调用getSum ,减少了不必要的函数调用和渲染,实现了优化。
注意:
useMemo 会返回一个值,所以写的是<div>sum:{getSum}</div> 而不是<div>sum:{getSum()}</div> ,不用自己调用,体会下这里的区别。- 传入
useMemo 的函数会在渲染期间执行,不要在这个函数内部执行与渲染无关的操作。这里只是为了演示,在函数内执行了console.log() 。 - 不要忘记写正确的依赖数组。若没有提供依赖数组,
useMemo 在每次渲染时都会计算新的值。
2. useCallback
useCallback 用来缓存函数。通常用于父子组件中,父组件传给子组件一个函数,父组件更新时,传递给子组件的函数也会被重新创建。有时候传递给子组件的函数没必要重新创建,useCallback 就可以缓存这个函数,不使这个函数重新被创建。
如下代码所示,父组件A 内部创建了num 和count 两个变量,显示在页面上,并可以更新它们的值。创建了一个函数getCount ,返回count 的值。父组件A 向子组件B 传递getCount 这个函数。
子组件B 拿到getCount 函数后,调用并将count 值显示在页面上。为了演示每次更新时,getCount 是否被重新创建,这里使用了Set 这个数据结构。在B 组件内部将getCount 函数的引用存入Set ,并显示Set 的长度,长度增加则说明getCount 函数被重新创建了。
import { useState } from 'react';
const set = new Set();
const A = () => {
const [num, setNum] = useState(0);
const [count, setCount] = useState(0);
const getCount = () => count;
return (
<>
<B getCount={getCount} />
<div>
count:{count}
<button onClick={() => setCount(count => count + 1)}>count++</button>
</div>
<div>
num:{num}
<button onClick={() => setNum(num => num + 1)}>num++</button>
</div>
</>
);
};
const B = ({ getCount }) => {
set.add(getCount);
return (
<>
<div>count:{getCount()}</div>
<div>集合内元素数量:{set.size}</div>
</>
);
};
export default A;
结果如下图所示,当num 发生变化时,触发父组件A 更新,传递给B 的getCount 函数也被重新创建了。然而getCount 函数返回的是count ,num 发生变化没必要再重新创建一次getCount 函数,这造成了性能的浪费。
可以使用useCallback 进行性能优化,在创建getCount 函数时,使用useCallback 包裹,并写上依赖数组[count] ,表示只有当count 变化时,才重新创建一次getCount 函数。
import { useState, useCallback } from 'react';
const set = new Set();
const A = () => {
const [num, setNum] = useState(0);
const [count, setCount] = useState(0);
const getCount = useCallback(() => count, [count]);
return (
<>
<B getCount={getCount} />
<div>
count:{count}
<button onClick={() => setCount(count => count + 1)}>count++</button>
</div>
<div>
num:{num}
<button onClick={() => setNum(num => num + 1)}>num++</button>
</div>
</>
);
};
const B = ({ getCount }) => {
set.add(getCount);
return (
<>
<div>count:{getCount()}</div>
<div>集合内元素数量:{set.size}</div>
</>
);
};
export default A;
结果如下图所示,count 更新时,传递给B 组件的getCount 函数被重新创建。而num 更新时,getCount 函数不会被重新创建,这减少了不必要的创建函数开销,实现了优化。
注意:
- 因为
useCallback 返回的是一个函数,所以页面上还是要写<div>count:{getCount()}</div> ,需要自己调用。体会一下与useMemo 的区别。
以上是本人学习所得之拙见,若有不妥,欢迎指出交流!
📘📘欢迎在我的博客上访问: https://lzxjack.top/
|