# useMemo 和 useCallback
Table of Contents
useCallback 和 useMemo 是 react 里面 Hook 的一部分,通常被作为性能优化使用。它们可以用来缓存函数、组件、变量,以减少两次渲染的重复计算。
useMemo
useMemo 将函数和数组作为参数,返回一个 memoized 值,仅当数组的某项改变时才会更新。
const memoizedValue = useMemo(() => computedExpensiveValue(a, b), [a, b])const [m, n] = useMemo(() => { // doSomething return [m, n]}, [a])传入的函数会在渲染期间执行,如果没有提供依赖数组,useMemo 在每次渲染时都会计算新的值。useMemo 与 useCallback 相比,useCallback 缓存是函数,useMemo 缓存是函数返回值,所以可以在函数内计算一些复杂的操作。
PS: useMemo 跟 React.memo 没有什么关系。
useCallback
useCallback 将回调函数和数组作为参数,返回一个 memoized 的回调函数,仅当数组的某项改变时才会更新。
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
const memoizedCallback = useCallback(() => { doSomething(a, b)}, [a, b])useMemo 和 useCallback 的作用
使用 memo 通常有三个原因:
- 防止不必要的重复渲染
- 防止不必要的重复计算
- 防止不必要的 effect
至于为什么不给所有的组件都使用 useMemo,上文已经解释了。useMemo 是有成本的,它会增加整体程序初始化的耗时,并不适合全局全面使用,它更适合做局部的优化。
防止不必要的重新渲染
当父组件重新渲染时,所有的子组件会被重新渲染,形成一条重新渲染链。
我们必须缓存回调函数和组件本身才可以实现不重复渲染,使用 useCallback 缓存回调函数,使用 React.memo 缓存组件。可以参考如下代码
import React, { useCallback, useState, memo } from 'react'
interface IProps { field: string foo?: () => void}
// 使用 React.memo 缓存组件const Child = memo(function (props: IProps) { console.log(`${props.field} render`) return <button onClick={props.foo}>ChildWithMemo</button>})
export default function App() { const [count, setCount] = useState(0) const foo = () => { console.log('foo') }
// 使用 useCallback 缓存回调函数 const fooCallback = useCallback(() => { console.log('foo') }, [])
return ( <> <div> <button onClick={() => setCount(count + 1)}>Increase</button> <span>count: {count}</span> </div> <div> <Child field="child" foo={foo} /> <Child field="childUseCallback" foo={fooCallback} /> <Child field="childNoRef" /> </div> </> )}当点击 Increase 按钮的时候,父组件进行刷新。使用 React.memo 优化了子组件,可以避免不必要的渲染。
- childNoRef 没有引用的父组件函数可以避免刷新,React.memo 缓存了组件。
- child 引用了父组件函数,当父组件刷新时,引用函数会进行刷新,因此子组件也会进行刷新。
- childUseCallback 使用 useCallback 缓存了回调函数。当父组件刷新时,传递的函数被记忆了下来,不会触发刷新。
防止不必要的计算
在重新渲染之间缓存一个需要大量计算的值。
import { useMemo } from 'react'
function TodoList({ todos, tab, theme }) { const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]) // ...}防止不必要的 effect
当一个值被当作另一个 Hook 的依赖项时,我们可以通过缓存该值防止重复执行 effect。
function Dropdown({ allItems, text }) { const searchOptions = useMemo(() => { return { matchMode: 'whole-word', text } }, [text]) // ✅ Only changes when text changes
const visibleItems = useMemo(() => { return searchItems(allItems, searchOptions) }, [allItems, searchOptions])
// ...}searchOptions 是 visibleItems 的依赖项,缓存 searchOptions 可以减少 visibleItems 的 effect 执行。