初学 React 的同学有时候会一头雾水,尤其是Hooks。 有时候会发现用不用它都能达到效果。这时候就很容易滥用 Hook 的场景。(这也可能只是我自己初学 React 的时候遇到的问题)。 所以打算捋一捋这些 Hook,到底什么时候才使用呢?先说一说 useCallback
和 useMemo
这两个,因为这两个总是会被面试官问到区别和用法。
useCallback
useCallback
是一个允许你再多次渲染中缓存函数的Hook。
精简一下:缓存函数。
用法:
ini
const cachedFn = useCallback(fn, dependencies)
fn: 想要缓存的函数。
dependencies :依赖项;当组件重新(不是首次) 渲染时,会调用 Object.is 判断依赖项是否发生变化;如果依赖项没有变化的时候,则 useCallback
回返回相同的函数。
注意
- 首先这是一个 Hook ,所以要遵循Hook的规则,只能在顶层中使用,不能在循环或者条件语句中调用。
- 我们看到
useCallback
的概念,可以知道他是用来缓存函数 的,所以这个Hook 是一种优化手段,避免组件重新渲染时重复创建相同的函数,从而优化性能。
什么时候用?
使用 useCallback
缓存函数仅在少数情况下有意义:
- 将其作为 props 传递给包装在 [
memo
] 中的组件。如果 props 未更改,则希望跳过重新渲染。缓存允许组件仅在依赖项更改时重新渲染。 举个例子:
javascript
function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
// ...
return (
<div className={theme}>
<ShippingForm onSubmit={handleSubmit} />
</div>
);
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
// ...
});
首先我们知道,默认情况下,当一个组件重新渲染时, React 将递归渲染 它的所有子组件。如果使用 memo 包裹子组件,如果props相同,子组件将不会重新渲染;
分析下上面这段代码:
handleSubmit 作为参数传递给了子组件。 如果此时 handleSubmit 没有使用 useCallback 进行缓存, 那当 theme 更新之后,组件重新渲染,也就意味着函数重新执行,handleSubmit也就重新创建了,这会导致传递给子组件的props永远都不会是相同, 此时 memo 的性能优化永远也不会生效。
- 传递的函数可能作为某些 Hook 的依赖。比如,另一个包裹在
useCallback
中的函数依赖于它,或者依赖于 useEffect中的函数。 举个例子:
scss
import React, { useEffect, useState, useCallback } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
// 使用 useCallback,确保 handleClick 函数在 count 不变时不重新创建
const handleClick = useCallback(() => {
console.log("Clicked", count);
}, [count]); // 仅在 count 变化时更新 handleClick
// 使用 useEffect,依赖 handleClick 函数
useEffect(() => {
console.log('Effect triggered!');
handleClick();
}, [handleClick]); // 现在只有 handleClick 变化时,useEffect 才会重新执行
return <button onClick={() => setCount(count + 1)}>Increment</button>;
}
在这个例子中,useEffect
依赖于 handleClick
函数。每当 handleClick
发生变化时,useEffect
会重新执行。如果handleClick
不被 useCallback 包裹每次渲染时都会被重新创建,因此 useEffect
也会被多次调用。
其他情况下,函数包装在useCallback
没有任何意义!!! ;谨记!!
其他情况下,函数包装在useCallback
没有任何意义!!! ;谨记!!
其他情况下,函数包装在useCallback
没有任何意义!!! ;谨记!!
清楚了useCallback之后,useMemo 也就很好理解了。
useMemo
useMemo
是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果。
精简一下:缓存结果。
用法:
ini
const cachedValue = useMemo(calculateValue, dependencies)
calculateValue: 缓存的值。
dependencies :依赖项;当组件重新(不是首次) 渲染时,会调用 Object.is 判断依赖项是否发生变化;如果依赖项没有变化的时候,则 useMemo
回返回相同的值。
什么时候用?
使用 useMemo
进行优化仅在少数情况下有价值:
- (和 useCallback 提到的两个场景相似, 只不过把函数 换成了值);
- 你在
useMemo
中进行的计算明显很慢 ,而且它的依赖关系很少改变。
javascript
const ExpensiveComponent = ({ data }) => {
// 假设计算非常复杂,可能需要几秒钟
const expensiveCalculation = (data) => {
console.log("Recomputing expensive calculation...");
return data.reduce((acc, num) => acc + num, 0); // 假设这是个复杂的计算
};
// 使用 useMemo 只在 data 改变时重新计算
const memoizedResult = useMemo(() => expensiveCalculation(data), [data]);
return <div>Result: {memoizedResult}</div>;
};
export default ExpensiveComponent;
注意:这句话中提到了两点,上面代码说明了计算很慢的场景, 还有一点就是依赖关系很少改变,依赖频繁变化,就没有缓存的意义了,此时使用 useMemo 还带来了额外的开销,因为本身执行的时候也是需要开销的。
总结
- 首先
useCallback
和useMemo
都是 React 中的优化 Hook,都是只应作用于性能优化。 useCallback
缓存的是函数。useMemo
缓存的是计算结果。- 我们应该只在少场景下使用他们,不能滥用(展开讲讲,参考上文)
文中有不正确的地方多谢指正。