useCallback、useMemo、React.memo

本文由 AI 协助更新

本文说明三个与「记忆 / 稳定引用」相关的 API:useCallbackuseMemoReact.memo,用于在需要时保持函数或值的引用稳定、或减少重复计算。若你主要关心性能,新版 React 可借助 React Compileropen in new window 自动做 memoization,从而减少手写这三者的需要。


useCallback

const memoizedCallback = useCallback(() => {
  doSomething(a, b)
}, [a, b])

返回一个在依赖不变时引用稳定的函数。

需要把回调传给经 React.memo 包装的子组件时可用,但在 React Compileropen in new window 下,这类场景多数可省略手写。

若函数是作为 useEffect 等 Hook 的依赖,更推荐用 useEffectEvent


useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])

在依赖不变时返回同一引用的结果。

保持对象/数组引用稳定

父组件每次渲染都新建对象或数组时,子组件即使用 React.memo 也会因 props 引用每次都是新的而重渲染。用 useMemo 在对象等同时返回同一引用即可。使用 React Compileropen in new window 时,这类场景通常可由编译器自动优化,无需手写 useMemo。

Effect 依赖的引用稳定:避免重复订阅

Effect 依赖「筛选后的列表」做订阅时,若列表每次渲染都是新数组,依赖会每次都变,导致重复订阅/取消。用 useMemo 让列表在 todosfilter 未变时保持同一引用,Effect 只在列表真正变化时执行。

function App() {
  const [todos, setTodos] = useState<Todo[]>([])
  const [filter, setFilter] = useState('all')

  const filteredTodos = useMemo(() => {
    if (filter === 'all') return todos
    return filter === 'active'
      ? todos.filter((t) => !t.completed)
      : todos.filter((t) => t.completed)
  }, [todos, filter])

  useEffect(() => {
    subscribeToTodos(filteredTodos)
    return () => unsubscribeFromTodos(filteredTodos)
  }, [filteredTodos])
}

缓存计算结果(引用稳定 + 少算一次)

当派生数据既作为 props 传给 memo 子组件,又确实计算较贵时,useMemo 同时起到「稳定引用」和「避免重复计算」的作用。

const filteredTodos = useMemo(() => {
  return todos
    .filter((todo) => (filter === 'all' ? true : todo.length > 3))
    .sort((a, b) => a.localeCompare(b))
}, [todos, filter])

return filteredTodos.map((todo, index) => <Text key={index}>{todo}</Text>)

子节点/元素引用稳定

需要把「一段 JSX 或子组件」作为稳定引用传给父组件或用于依赖时,也可以用 useMemo 包一层(组件返回的是元素,见 认识组件和元素)。依赖未变时,该子节点不会重新创建。

const activeList = useMemo(() => <TodoList todos={activeTodos} />, [activeTodos])
const completedList = useMemo(() => <TodoList todos={completedTodos} />, [completedTodos])

何时不必用

简单计算(如 todos.length)或不会作为 props 传给 memo 子组件的值,不必为「少算一次」单独加 useMemo,直接计算即可。useMemo 本身有比较依赖的开销,只在「需要稳定引用」或「计算确实贵且依赖不变」时再用。


React.memo

const MemoizedComponent = React.memo(Component)

组件级的「记忆」:对 props 做浅比较,未变则复用上次渲染结果,不重新执行组件函数。与 useCallback / useMemo 配合时,才能发挥最大作用——父组件传给子组件的函数和对象引用稳定,memo 才能跳过重渲染。

API作用对象主要用途
React.memo组件props 未变时跳过渲染
useMemo值(对象/数组等)保持值的引用稳定
useCallback函数保持函数的引用稳定

基本用法

const MemoizedTodoItem = React.memo(function TodoItem({ title }: TodoItemProps) {
  console.log('TodoItem 渲染')
  return <Text style={styles.todoItem}>{title}</Text>
})

function App() {
  const [filter, setFilter] = useState('all')
  const [todos] = useState(['买菜', '写代码', '运动'])

  return (
    <View style={styles.container}>
      <Text>筛选: {filter}</Text>
      <Button title="切换筛选" onPress={() => setFilter(filter === 'all' ? 'active' : 'all')} />
      {todos.map((todo, index) => (
        <MemoizedTodoItem key={index} title={todo} />
      ))}
    </View>
  )
}

filter 变化时,todos 和每个 title 没变,MemoizedTodoItem 不会重渲染。

自定义比较

默认是浅比较 props。需要自定义时,传第二个参数:

const TodoItem = React.memo(
  ({ todo, onToggle }: TodoItemProps) => { /* ... */ },
  (prevProps, nextProps) =>
    prevProps.todo.id === nextProps.todo.id &&
    prevProps.todo.completed === nextProps.todo.completed
)

返回 true 表示视为相等,不重渲染。

与 useMemo / useCallback 一起用

父组件用 useMemo 稳定对象/数组 props,用 useCallback 稳定回调 props,子组件用 React.memo,才能稳定跳过重渲染。

const TodoItem = React.memo(({ todo, onToggle }: TodoItemProps) => { /* ... */ })

function App() {
  const [todos, setTodos] = useState<Todo[]>([...])
  const [filter, setFilter] = useState('all')

  const filteredTodos = useMemo(() => {
    if (filter === 'all') return todos
    return filter === 'active' ? todos.filter((t) => !t.completed) : todos.filter((t) => t.completed)
  }, [todos, filter])

  const handleToggle = useCallback((id: number) => {
    setTodos((prev) =>
      prev.map((todo) =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    )
  }, [])

  return (
    <View style={styles.container}>
      <Button title="切换筛选" onPress={() => setFilter(filter === 'all' ? 'active' : 'all')} />
      {filteredTodos.map((todo) => (
        <TodoItem key={todo.id} todo={todo} onToggle={handleToggle} />
      ))}
    </View>
  )
}

不要滥用

只对「渲染成本高、且 props 不经常变」的组件包 React.memo。简单展示型组件不必包。


性能优化与 React Compiler

使用本节 API 时建议:

  1. 先保证正确性—— 避免昂贵计算,或避免 Effect 依赖抖动,而不是为了「看起来在优化」。
  2. 不为了「省一次计算」滥用——简单计算或非 props 的值不必包一层;不确定时可以不写,交给 React Compiler 或后续用 Profiler 再优化。
  3. 优化有成本——依赖比较、缓存都有开销,只在有明确收益时使用。

目录

  1. 组件 — 认识组件和元素、Props、State、单向数据流
  2. useState、useReducer、useContext
  3. useEffect、useEffectEvent
  4. 闭包陷阱、useRef
  5. useCallback、useMemo、React.memo
  6. Hook 规则、自定义 Hook
上次更新: