Published on

useRef、useMemo 和 useCallback 详解

Authors
  • avatar
    Name
    Reeswell
    Twitter

概述

useRef 用于在函数组件中访问 DOM 或保持一个可变的引用值,它不会触发重新渲染;
useMemo 用于记忆计算结果,避免重复计算,优化性能;
useCallback 用于记忆函数本身,防止不必要的子组件重新渲染。

解答思路

理解每个 Hook 的作用

  • useRef: 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(初始值)
  • useMemo: 返回一个 memoized 值,在依赖项变化时才会重新计算
  • useCallback: 返回一个 memoized 函数,在依赖项变化时才会重新生成函数

分析使用场景

  • useRef: 多用于获取 DOM 节点、保存状态、计数器等不需要触发重渲染的数据
  • useMemo: 适用于开销较大的计算逻辑,如数据处理、格式转换等
  • useCallback: 主要用于将函数作为 props 传递给子组件时,避免每次渲染都创建新函数导致子组件不必要的更新

比较优缺点

  • useRef: 轻量级,不触发重渲染,但需注意不要滥用作状态管理,否则会破坏 React 数据流
  • useMemo: 节省计算资源,但增加内存占用;过度使用可能导致代码难以维护
  • useCallback: 避免子组件不必要的重渲染,但增加了函数记忆的成本,也可能掩盖设计问题

深度知识讲解

一、useRef

底层原理

React 内部为每个组件实例维护了一个 ref 对象,其 .current 属性是可变的,并且不会引起组件重新渲染。

使用场景

  • 获取 DOM 元素(如 input.focus()
  • 保存上一次的状态(比如 prevPropsprevState
  • 计数器、标志位等无需触发渲染的数据

示例代码

function TextInputWithFocusButton() {
  const inputEl = useRef(null)

  const onButtonClick = () => {
    inputEl.current.focus()
  }

  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  )
}

二、useMemo

底层原理

当依赖项未发生变化时,直接返回上次计算的结果;只有当依赖项变化时才执行回调函数重新计算。

使用场景

  • 渲染昂贵的 UI 元素前的数据预处理
  • 衍生数据(如过滤、排序后的列表)

示例代码

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

三、useCallback

底层原理

与 useMemo 类似,只是缓存的是函数本身。返回的函数引用保持不变,除非依赖项改变。

使用场景

  • 将函数作为 props 传递给优化过的子组件(如 React.memo 包裹的组件)
  • 避免因为函数引用变化而引发的副作用重复执行

示例代码

const handleClick = useCallback(() => {
  console.log(`Clicked with value: ${value}`)
}, [value])

// 子组件
const MemoizedChild = React.memo(({ onClick }) => <button onClick={onClick}>Click me</button>)

扩展知识

1. 性能优化误区

很多人误以为使用 useMemouseCallback 一定能提升性能,其实不然。React 官方文档也强调:只在遇到性能瓶颈时才使用这些优化手段

2. React.memo 与 useCallback 的配合

如果你将一个函数作为 props 传给子组件,而该子组件用 React.memo 进行了优化,那么必须使用 useCallback 来保证函数引用不变,才能让 React.memo 生效。

3. useEffect 中的依赖项陷阱

useEffect 中使用函数时,如果函数没有用 useCallback 包裹,可能会导致闭包中的值不是最新的。这种情况下应该使用 useRef 来保存最新值或使用函数式更新方式。

总结对比表

Hook是否触发重渲染缓存内容适用场景优点缺点
useRef可变引用对象获取 DOM、保存状态不触发渲染、简单高效易滥用造成状态混乱
useMemo计算结果消耗大的数据处理避免重复计算占用额外内存、可能得不偿失
useCallback函数引用传递给子组件、useEffect 中稳定依赖避免子组件不必要渲染增加复杂度、可能掩盖结构问题

结语

合理使用这三个 Hook 是写好 React 函数组件的关键之一。记住以下几点:

  1. 优先使用 state 管理数据流
  2. useRef 用于保存"状态之外"的可变数据
  3. useMemo 和 useCallback 是性能优化工具,不应过早使用
  4. 在大型项目中,建议结合 ESLint 插件(如 eslint-plugin-react-hooks)来确保依赖项的完整性

掌握它们的本质原理和底层机制,有助于写出更健壮、高效的 React 应用程序。