- Published on
useRef、useMemo 和 useCallback 详解
- Authors
- Name
- Reeswell
概述
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 函数组件的关键之一。记住以下几点:
- 优先使用 state 管理数据流
- useRef 用于保存"状态之外"的可变数据
- useMemo 和 useCallback 是性能优化工具,不应过早使用
- 在大型项目中,建议结合 ESLint 插件(如
eslint-plugin-react-hooks
)来确保依赖项的完整性
掌握它们的本质原理和底层机制,有助于写出更健壮、高效的 React 应用程序。