useMemo
useMemo
useMemo 是拿来保持一个对象引用不变的。useMemo 和 useCallback 都是 React 提供来做性能优化的。比起 classes, Hooks 给了开发者更高的灵活度和自由,但是对开发者要求也更高了,因为 Hooks 使用不恰当很容易导致性能问题。
假设有个 component,在 dataConfig 变化的时候重新去 fetchData:
1 | <Child |
如果是个 Class Component,会这么写:
1 | class Child extends React.Component<Props> { |
使用 Hooks 后长这样:
1 | const Child = ({ fetchData, dataConfig }: Props) => { |
使用 Class Component 时我们需要手动管理依赖,但是使用 Hooks 时会带来副作用:React 使用的是Object.is()
,如果fetchData
的 reference 变了,也会触发 useEffect
。
虽然逻辑上 React 的处理是合理的,但是还是需要手动去解决它导致的性能问题:官方提供了 useCallback 这个 hooks,用于解决函数引用问题。
1 | const App = () => { |
但是这个时候还有一个地方没有解决——Props。只要 props 更新,组件还是会重新 fetchData,因为 dataConfig 也是一个会变化的 prop。memo 是一个最容易被忽略的 Hooks,即使我们有意不在 JSX 中做计算,写成这样:
1 | const fetchData = useCallback( |
由于习惯函数式编程,我们已经习惯了这种写法。但是组件是有状态的,状态更新了就得处理相关逻辑,触发 re-render。我们需要告诉 React 什么时候该处理这个状态,这时候 useMemo 就登场了。
1 | const fetchData = useCallback( |
这样 dataConfig 只有在 getId 或 queryId 变化时才重新生成,组件才会在必要的时候重新 fetchData
memo
只使用 useMemo 和 useCallback 来进行优化是有可能达不到效果的,原因在于如果 props 引用不发生变化,虽然不会重新渲染,但它依然会重新执行。
1 | const Child = ({ name }: Props) => { |
如果 Child 中的计算量非常大,这时候的性能主要就耗在重新执行的这个过程了。如果想要阻断这一过程重新执行,React 有一个 API:memo,它相当于一个 PureComponent,是一个 HOC,默认对 props 进行一次浅比较,如果 props 不变,则不会重新执行。
现在给 Child 套上 memo:
1 | const Child = memo(({ name }: Props) => { |
或者使用 useMemo 包裹:
1 | const App = () => { |
何时何处使用?
- 开销大的组件可以考虑使用 memo。因为有的组件重新渲染的开销可能比用 memo 做浅比较的开销还小,但是如果组件的重新执行开销很大,使用 memo 一定可以加快性能。
- 用 useMemo 和 useCallback 来控制 props 的引用,和 memo 配套使用效果最佳。性能优化是一个整体的过程,不是单独在某个组件里就可以改善的。
- useMemo 避免昂贵计算,useCallback 解决 reference 问题,memo 解决 shouldComponentUpdate 问题。