背景
useCallback与useMemo在react中用来缓存函数与对象,但性能优化也会有成本,缓存过多时会占用内存过多,垃圾回收器不会及时释放,变成了负优化。因此,在大多数情况都不应该使用。
什么时候使用
1. 子组件使用useEffect,并且依赖父组件传进来的props
请看以下例子
// 子组件
function Foo({bar, baz}) {
React.useEffect(() => {
const options = {bar, baz}
buzz(options)
}, [bar, baz])
return <div>foobar</div>
}
// 父组件
function Blub() {
const bar = () => {}
const baz = [1, 2, 3]
return <Foo bar={bar} baz={baz} />
}
子组件Foo使用了useEffect并且依赖父组件Blub传递的参数bar与baz,在这种情况下只要父组件一更新,子组件的useEffect必调用,尽管参数值没有改变,原因是引用变了。那么如何解决这个问题呢?请看以下代码。
// 子组件
function Foo({bar, baz}) {
React.useEffect(() => {
const options = {bar, baz}
buzz(options)
}, [bar, baz])
return <div>foobar</div>
}
// 父组件
function Blub() {
const bar = React.useCallback(() => {}, [])
const baz = React.useMemo(() => [1, 2, 3], [])
return <Foo bar={bar} baz={baz} />
}
在父组件中引入了useCallback与useMemo来包裹传递给子组件的参数,这样就算父组件更新了,useEffect依赖的参数引用不变,就不会调用多次。
2. 子组件使用了React.memo时
请看以下例子
// 子组件
const CountButton = React.memo(function CountButton({onClick, count}) {
return <button onClick={onClick}>{count}</button>
})
// 父组件
function DualCounter() {
const [count1, setCount1] = React.useState(0)
const increment1 = () => setCount1(c => c + 1)
const [count2, setCount2] = React.useState(0)
const increment2 = () => setCount2(c => c + 1)
return (
<>
<CountButton count={count1} onClick={increment1} />
<CountButton count={count2} onClick={increment2} />
</>
)
}
子组件使用了React.memo,希望在props不改变的情况下,父组件更新了,子组件也不会更新,从而实现性能优化。但是在本例中只要父组件更新,子组件就会更新2次,原因是子组件使用了父组件的函数,父组件更新函数就更新,由于memo是浅比较,函数更新子组件就会更新,因此达不到性能优化的效果。解决方案如下
// 子组件
const CountButton = React.memo(function CountButton({onClick, count}) {
return <button onClick={onClick}>{count}</button>
})
// 父组件
function DualCounter() {
const [count1, setCount1] = React.useState(0)
const increment1 = React.useCallback(() => setCount1(c => c + 1), [])
const [count2, setCount2] = React.useState(0)
const increment2 = React.useCallback(() => setCount2(c => c + 1), [])
return (
<>
<CountButton count={count1} onClick={increment1} />
<CountButton count={count2} onClick={increment2} />
</>
)
}
父组件使用了useCallback来包裹函数,使得父组件在更新使函数的引用不变,因此子组件不会更新,从而达到了性能优化的效果。如果参数是对象则使用useMemo。
3. 组件使用昂贵计算的时候
请看以下代码
function RenderPrimes({iterations, multiplier}) {
const primes = calculatePrimes(iterations, multiplier)
return <div>Primes! {primes}</div>
}
假设calculatePrimes是一个很复杂的函数,要计算很久才能返回结果,这种情况下有必要使用useMemo来优化组件。修改后代码如下
function RenderPrimes({iterations, multiplier}) {
const primes = React.useMemo(() => calculatePrimes(iterations, multiplier), [
iterations,
multiplier,
])
return <div>Primes! {primes}</div>
}
如果每次计算出的结果相同,那么就不会每次都去调用calculatePrimes去做复杂的计算了,只需要使用上一次计算的结果即可。
总结
以上是我整理出的使用useCallback与useMemo的三种情况,如果有还有其他场景欢迎大佬补充。