React Hooks组件化开发——useMemo&&useCallback
基于useMemo构建计算缓存
useMemo
是一个 React hook,它用于在组件内部缓存计算的结果。当你想要避免在每次渲染时都进行高代价的计算时,这个 hook 就非常有用。useMemo
会在依赖项没有发生变化的情况下,返回缓存的值,避免不必要的计算。
应用场景——复杂逻辑避免每次状态更新重新执行
1 | /* |
每次状态修改(任何状态修改)都会重新执行函数组件,生成新的闭包,函数内的逻辑自上而下都会重新执行,上面示例中计算正确率
实际上只与correct
,wrong
有关,其余任何状态的改变都不必执行计算正确率
(影响性能),使用useMemo
可以避免在每次组件渲染时都执行复杂的计算。
在依赖的状态值没有改变,callback没有触发执行的时候,接收返回参数的变量获取的是上一次计算出来的结果。
然而,它并不保证缓存一直不变,因为 React 可能会决定“忘记”一些之前缓存的值以释放内存。因此,不应该依赖 useMemo
来处理副作用,而仅仅用于优化性能。如果你需要处理副作用,应该使用 useEffect
或其他适用的 hook。
基于useCallBack缓存函数引用
useCallback
是 React 的一个 hook,它返回一个记忆化的回调函数。这个 hook 在将回调函数传递给经过优化的子组件并且希望避免不必要的子组件重渲染时特别有用。useCallback
会在依赖项没有改变的情况下返回同一个回调函数实例,从而避免因为函数引用的变化而触发子组件的重渲染。
应用场景——父组件嵌套子组件时,父组件更新导致子组件重新渲染
父组件
1 | const UseCallBackDemo = () => { |
子组件
1 | class Son extends React.Component { |
上述示例父组件每次状态更新重新执行函数组件,内部faChange
函数的内存引用地址都会重新生成,faChange
作为子组件的props
引用地址不同会导致子组件每次随着父组件状态更新而重新渲染。
性能优化:当父组件更新时,由于传递给子组件的是函数,因此不让子组件随着父组件更新而更新
- 基于useCallback处理,使传递给子组件的函数每次引用地址都一致
- 子组件内部,验证父组件传递的属性是否发生改变,没有变化,子组件则不更新,有变化则更新。
- 子组件为类组件:继承
React.PureComponent
- 子组件为函数组件:使用
React.Memo
- 子组件为类组件:继承
父组件
1 | const UseCallBackDemo = () => { |
子组件
1 | // class组件 |
基于应用场景使用
不是所有的小函数都需要用 useCallback
包起来。是否使用 useCallback
取决于具体的使用场景和性能优化需求。以下是一些决定是否使用 useCallback
的指导原则:
传递给纯组件:如果你正在将一个回调函数传递给一个经过优化的子组件(如
React.memo
包装的组件),并且你希望防止这个子组件因为父组件的渲染而进行不必要的重渲染,那么应该使用useCallback
。渲染优化:在有大量渲染操作或列表,并且需要避免不必要的重新渲染时,使用
useCallback
可以提升性能。依赖稳定性:当函数被作为依赖传递给其他
useEffect
、useMemo
或useCallback
时,为了避免因为函数引用的变化而触发重执行,应该使用useCallback
。事件处理器:如果事件处理器被频繁触发,使用
useCallback
可以避免由于组件重渲染而导致的事件处理器重复创建。
然而,如果这个函数:
- 不被传递给子组件,
- 不作为依赖项被其他 hooks 使用,
- 不是在经过性能优化的组件中使用,或者
- 即使父组件重渲染,也不会导致性能瓶颈,
那么将它包装在 useCallback
中可能是不必要的。实际上,在这些情况下使用 useCallback
可能会引入额外的性能开销,因为记忆化函数也是有成本的。
因此,尽管 useCallback
可以避免不必要的渲染,但它并不是一个万能的解决方案,而应该基于性能优化的需要来决定是否使用。在很多情况下,简单的函数组件不使用 useCallback
也能很好地工作。