在 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发生变化时,才重新计算一次
}, [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/