什么时候用useCallback与useMemo

6,103 阅读2分钟

背景

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的三种情况,如果有还有其他场景欢迎大佬补充。

参考文章

jancat.github.io/post/2019/t…