一个简单的例子,memo,useCallback,useMemo避免不必要的子组件重渲染

331 阅读2分钟

背景

首先我们定义一个Compound Component:

const App = () => {
  console.log('App render.')
  const [count, setCount] = useState(0)
  const someValue = 'someValue'

  return (
    <div>
      <button onClick={() => setCount(prev=>prev+1)}>
        click to rerender
      </button>
      <CountDemo count={count} />
      <Demo someValue={someValue} />
    </div>
  )
}

两个子组件分别为:

const CountDemo = ({count}) => {
  console.log('count render.')
  return (
    <div>
      <p>{count}</p>
    </div>
  )
}

const Demo = () => {
  console.log('Demo render.')
  return (
    <div>Demo</div>
  )
}

当我们点击按钮时,控制台会显示:

"App render."
"count render."
"Demo render."

可以看到CountDemoDemo都会重新渲染,而实际上,需要更新的只有CountDemo

memo

可以使用React.memo避免Demo组件重复渲染:

// 使用React.memo
const Demo = memo(() => {
  console.log('Demo render.')
  return (
    <div>React.memo</div>
  )
})

点击按钮,控制台显示:

"App render."
"count render."

避免对demo进行不必要的重渲染。

useCallback

如果传入子组件的不是基本类型数据,而是一个函数呢?

const App = () => {
 ...
const someValue = 'someValue'
const someFunc = () => someValue
 ...
    <Demo someFunc={someFunc} />
 ...
}

const Demo = memo(({someFunc}) => {
  ...
  return (
    <div>{someFunc()}</div>
  )
})

点击按钮,控制台显示:

"App render."
"count render."
"Demo render."

因为React.memo默认使用的是浅比较(shallowly compare),而函数是引用型数据,父组件每次重新渲染时都会生成不一样的someFunc,从而导致demo会发生重渲染。 所以,可以使用useCallback来返回一个memoized callback:

const App = () => {
 ...
// 使用useCallback,当someValue改变时才会改变
const someFunc = useCallback(() => someValue, [someValue])
 ...
    
}

此时点击按钮,demo组件不会出现不必要的重复渲染。

useMemo

若是传入一个对象,而对象也是引用型数据,那么与useCallback类似,我们可以使用useMemo避免不必要的重渲染:

const App = () => {
 ...
const someValue = 'someValue'
const someObj = useMemo(() => ({ value: someValue }), [someValue])
 ...
    <Demo someFunc={someFunc} />
 ...
}

const Demo = memo(({someObj}) => {
  ...
  return (
    <div>{someObj.value}</div>
  )
})

线上示例

See the Pen React Demo by eehong (@eugene930) on CodePen.

参考

Hooks API Reference

Hooks Cheatshhet