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())
  • 保存上一次的状态(比如 prevProps 或 prevState)
  • 计数器、标志位等无需触发渲染的数据

示例代码

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. 性能优化误区

很多人误以为使用 useMemo 和 useCallback 一定能提升性能,其实不然。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 应用程序。