useMemo
和 useCallback
都是 React 的优化 Hooks,它们的主要目的是提高性能和避免不必要的计算或函数创建。那么它们又有哪些不同点呢?今天就让我们一起来讨论一下吧!
useMemo:
useMemo
通过缓存计算结果来避免在每次渲染时都进行昂贵的计算。当你有一个计算或者对象构造函数,其结果依赖于某些输入(依赖项),你可以使用 useMemo
来存储这个结果。当依赖项没有改变时,React 将重用之前计算的结果,而不是重新计算。
工作原理:
- 在组件 render 期间,
useMemo
接收一个创建函数和一个依赖项数组作为参数。 - 如果依赖项数组中的值与上一次 render 时的值相比没有任何变化,
useMemo
就会返回上次计算的结果。 - 如果依赖项中有任何值发生了变化,
useMemo
就会重新执行创建函数,并将新的结果缓存起来,然后返回这个新结果。
TypeScript 代码实例:
typescript
import { useMemo } from 'react';
interface Props {
array: number[];
}
function ComplexCalculation({ array }: Props) {
// 使用 useMemo 缓存排序后的数组
const sortedArray = useMemo(() => {
// 这是一个复杂的计算或者对象构造函数
return array.sort((a, b) => a - b);
}, [array]);
// 当 array 变化时,sortedArray 才会重新计算
console.log('Sorted Array:', sortedArray);
return (
<div>
{sortedArray.map(num => (
<p key={num}>{num}</p>
))}
</div>
);
}
在这个例子中,我们有一个 ComplexCalculation
组件,它接收一个数字数组作为属性。我们使用 useMemo
来缓存排序后的数组。只有当 array
属性发生变化时,才会重新计算 sortedArray
。
这样做的好处是,如果 array
在多次渲染中保持不变,我们就无需每次都对数组进行排序,从而提高了性能。同时,由于 sortedArray
是在 useMemo
中计算的,所以它不会影响到 React 的渲染过程,保证了组件的状态管理。
useCallback:
useCallback
主要用于缓存函数。当你有一个需要在 render 或者事件处理等场景中使用的函数,并且你希望在依赖项没有改变时保持该函数的引用不变,可以使用 useCallback
。
以下是一个详细的 useCallback
工作原理和 TypeScript 示例:
工作原理:
- 在组件 render 期间,
useCallback
接收一个创建函数和一个依赖项数组作为参数。 - 如果依赖项数组中的值与上一次 render 时的值相比没有任何变化,
useCallback
就会返回上次创建的函数引用。 - 如果依赖项中有任何值发生了变化,
useCallback
就会重新执行创建函数,并将新的函数引用缓存起来,然后返回这个新引用。
TypeScript 代码实例:
typescript
import { useCallback } from 'react';
interface Props {
onButtonClick: (value: string) => void;
}
function ButtonHandler({ onButtonClick }: Props) {
// 使用 useCallback 缓存 handleClick 函数引用
const handleClick = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
onButtonClick('Clicked');
},
[onButtonClick],
);
// 当 onButtonClick 变化时,handleClick 函数引用才会重新创建
console.log('Handle Click:', handleClick);
return (
<button onClick={handleClick}>
Click me
</button>
);
}
在这个例子中,我们有一个 ButtonHandler
组件,它接收一个回调函数 onButtonClick
作为属性。我们使用 useCallback
来缓存 handleClick
函数引用,该函数在按钮被点击时调用 onButtonClick
。
这样做的好处是,如果 onButtonClick
在多次渲染中保持不变,我们就无需每次都创建新的 handleClick
函数引用,从而降低了内存开销并提高了性能。同时,由于 handleClick
函数引用是稳定的,React 可以更高效地进行事件处理和比较,避免了不必要的子组件重新渲染。
拓展: 在某些情况下,你可能希望在 useCallback
中依赖其他状态或变量,但又不想触发函数引用的重新创建。这时,你可以使用 useRef
来存储这些变量,并将其添加到 useCallback
的依赖项数组中。因为 useRef
返回的对象引用始终不变,所以即使其 .current
属性发生变化,也不会触发 useCallback
中函数引用的重新创建。
以下是一个使用 useRef
拓展 useCallback
的 TypeScript 示例:
typescript
import { useCallback, useRef } from 'react';
interface Props {
onButtonClick: (value: string) => void;
}
function ButtonHandler({ onButtonClick }: Props) {
const counterRef = useRef(0);
// 使用 useCallback 缓存 handleClick 函数引用
const handleClick = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
onButtonClick(`Clicked ${counterRef.current}`);
},
[onButtonClick], // 注意这里没有包含 counterRef
);
// 更新 counterRef 的 .current 属性,但不会触发 handleClick 函数引用的重新创建
useEffect(() => {
counterRef.current++;
}, []);
return (
<button onClick={handleClick}>
Click me
</button>
);
}
在这个扩展的例子中,我们在 useCallback
中依赖了一个通过 useRef
创建的计数器 counterRef
。尽管 counterRef.current
的值在每次渲染后都会增加,但由于我们只在 useEffect
中更新它,而没有将其添加到 useCallback
的依赖项数组中,因此 handleClick
函数引用不会因 counterRef
的变化而重新创建。这使得我们可以在保持函数引用稳定的同时,灵活地在函数内部访问和使用可变的状态或变量。
好了,今天的内容就到分享这里啦,很享受与大家一起学习,沟通交流问题,如果喜欢的话,请为我点个赞吧 !👍
作者:chenuvi
plus: 最近在看工作机会,base 上海,有合适的前端岗位希望可以推荐一下啦!