react性能优化之memo,useMemo,useCallback

1,010 阅读3分钟

前言

性能优化一直是我们关心的问题,这一块也比价大,今天我们就先看一下react的一些hooks怎么来优化组件。

memo、useMemo、useCallback的基本用法

memo

React.memo(type, compare?)是一个高阶组件,接收两个参数,第一个参数是需要优化的组件,第二个是非必填的自定义的compare函数,如果不传则会使用默认的compare函数。通过compare比较新旧props是否“相同”,选择是重新渲染组件还是跳过渲染组件的操作并直接复用最近一次渲染的结果。

一般我们会使用默认的compare,但这个compare函数的基本原理就是通过Object.is()函数来进行浅比较的,这就意味着对于对象只有是同一个引用才会被认定为相同。所以单独用memo函数只能进行简单的优化,很容易出现对象值相同,但是两个不同的对象(引用不同)。这个时候就需要useMemo,useCallback进行相应的优化。(当然特定情况下自定义compare函数也可以)。

useCallback

useCallback(callback,deps)函数接受回调函数和依赖数组。它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染的子组件时,它将非常有用。比如上面的memo函数的默认compare函数。

useMemo

useMemo(create,deps)函数接受“创建”函数和依赖项数组,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。当然也可以配合memo来优化组件。

具体优化

  • 先来看一个普通的父子组件。
const Son = () => {
  console.log('son update!');
  
  return (
    <div className={styles.son}>
      son components
    </div>
  )
}

export default () => {
  console.log('father update!')
  const [count, setCount] = useState(0);

  return (
    <div className={styles.father}>
      <button onClick={() => { setCount(count + 1) }}>click</button>
      <Son />
    </div>
  );
}

image.png

每次点击,父子组件都会更新,这显然不是我们想看到的,因为子组件里面并没有用到count,count改变,子组件最好是不刷新的。

  • 用memo优化
> const Son = React.memo(() => {
  console.log('son update!');
  
  return (
    <div className={styles.son}>
      son components
    </div>
  )
> })

export default () => {
  console.log('father update!')
  const [count, setCount] = useState(0);

  return (
    <div className={styles.father}>
      <button onClick={() => { setCount(count + 1) }}>click</button>
      <Son />
    </div>
  );
}

这次点击就会发现子组件不会更新了达到了预期。但这种优化只能针对简单的传参,如果传入的参数是引用类型的值,就会因为父组件的重新执行,导致参数的引用地址发生变化,被认为是新的值。比如下面这种情况:

const Son = React.memo(() => {
  console.log('son update!');
  
  return (
    <div className={styles.son}>
      son components
    </div>
  )
})

export default () => {
  console.log('father update!')
  const [count, setCount] = useState(0);

  return (
    <div className={styles.father}>
      <button onClick={() => { setCount(count + 1) }}>click</button>
>       <Son
>        data={{}}
>       />
    </div>
  );
}

我们仅仅是传入一个空对象到子组件,这时候点击按钮发现子组件也会更新。默认情况下的memo函数的优化效果就失效了。这是由于点击调用setCount触发了父组件的更新,这时候子组件重新执行,传入一个新的空对象,由于引用地址发生变化,memo函数会认为子组件需要更新。

在用hooks的方式写组件的时候,父组件定义的事件处理函数在父组件每次更新渲染都会生成一个新的处理函数。子组件如果被传入次函数,memo函数会认为子组件需要更新。

总结

memo配合useMemo、useCallback可以很好的实现子组件的优化。但deps的对比,hooks的存储需要时间和空间,useMemo,useCallback也不要盲目使用。对于一些特殊更新的组件,自定义compare函数也不为一种方法。