useRef 有哪些常见的应用场景?
useRef 是 React Hooks 中一个独特且功能强大的工具。与 useState 不同,更新 useRef 的值不会触发组件的重新渲染。这个核心特性决定了它的主要用途:在需要一个“可变容器”来跨渲染周期持久化数据,但又不希望这个数据的变化影响视图时,useRef 就成了最佳选择。
本质上,我们可以把 useRef 看作是函数组件实例上的一个“储物箱”,我们可以随时存取物品,而不用每次都重新整理(渲染)整个房间。
下面我们来探讨几个最常见的应用场景。
访问和操作 DOM 元素
这是 useRef 最广为人知的用途。在 React 的声明式世界里,我们通常不直接操作 DOM。但总有一些场景需要我们“破例”,例如:
自动聚焦输入框
获取元素的尺寸或位置
控制视频或音频的播放
集成需要直接访问 DOM 的第三方库(如 D3.js)
在这些情况下,useRef 提供了一个可靠的桥梁。
工作流程:
创建一个
ref对象:const inputRef = useRef(null);将其附加到
JSX元素的ref属性上:<input ref={inputRef} />在
useEffect或事件处理函数中,通过inputRef.current访问该DOM节点。
示例:页面加载后自动聚焦输入框
import React, { useRef, useEffect } from 'react';
function AutoFocusInput() {
// 1. 创建一个 ref
const inputRef = useRef(null);
useEffect(() => {
// 3. 在 effect 中访问 DOM 节点
// inputRef.current 指向的就是真实的 <input> DOM 元素
if (inputRef.current) {
inputRef.current.focus();
}
}, []); // 空依赖数组确保 effect 仅在组件挂载时运行一次
return (
<div>
<p>输入框将在加载后自动获得焦点:</p>
{/* 2. 将 ref 附加到 DOM 元素 */}
<input ref={inputRef} type="text" placeholder="请在这里输入..." />
</div>
);
}存储跨渲染周期的可变值
这是 useRef 更为精妙的用法。当我们有一个值,需要在多次渲染之间保持不变,但它的变化又不应该引起视图更新时,useRef 就非常适用。最典型的例子是管理定时器。
场景:管理 setTimeout 或 setInterval
如果我们将定时器 ID 存储在 useState 中,每次 setTimerId 都会导致不必要的重渲染。而使用 useRef,我们可以悄无声息地在后台存储和清除定时器。
示例:一个简单的秒表
import React, { useState, useRef } from 'react';
function Stopwatch() {
const [seconds, setSeconds] = useState(0);
// 使用 ref 来存储 interval ID
const intervalRef = useRef(null);
const handleStart = () => {
// 防止重复启动
if (intervalRef.current) return;
intervalRef.current = setInterval(() => {
// 注意:这里使用函数式更新,避免依赖外部的 `seconds` 变量
setSeconds(prevSeconds => prevSeconds + 1);
}, 1000);
};
const handleStop = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null; // 清理 ref
}
};
const handleReset = () => {
handleStop();
setSeconds(0);
};
return (
<div>
<h2>秒表: {seconds}s</h2>
<button onClick={handleStart}>开始</button>
<button onClick={handleStop}>停止</button>
<button onClick={handleReset}>重置</button>
</div>
);
}在这个例子中,intervalRef.current 的变化(从 null 变为一个数字 ID,再变回 null)完全独立于组件的渲染流程,实现了高效的状态管理。
缓存上一次的 Props 或 State
有时我们需要在渲染时拿到上一次的某个值,以便进行比较或计算。例如,我们想知道一个数字是增大了还是减小了。useRef 和 useEffect 的组合可以优雅地实现这个需求。
工作流程:
用
useRef创建一个 ref 来存储上一次的值。在
useEffect中,先执行逻辑(此时 ref 中还是上一次的值),然后更新 ref 为当前值,为下一次渲染做准备。
示例:显示当前值和上一个值
import React, { useState, useEffect, useRef } from 'react';
function PreviousValueDisplay({ value }) {
// 用 ref 存储上一次的 value
const prevValueRef = useRef();
useEffect(() => {
// effect 会在每次渲染完成后运行
// 所以在这次更新 ref 之前,它存储的还是上一次渲染的值
prevValueRef.current = value;
}, [value]); // 依赖 value,确保 value 变化时更新 ref
// 在渲染时,prevValueRef.current 还是上一次的值
const prevValue = prevValueRef.current;
return (
<p>当前值: {value}, 上一个值: {prevValue === undefined ? 'N/A' : prevValue}</p>
);
}
function App() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>增加</button>
<button onClick={() => setCount(c => c - 1)}>减少</button>
<PreviousValueDisplay value={count} />
</div>
);
}这个模式非常实用,可以看作是实现了一个自定义 Hook usePrevious 的基础。
useState vs useRef:如何选择?
为了更好地理解 useRef,我们可以建立一个清晰的选择标准:
| 特性 | useState | useRef |
|---|---|---|
| 触发重渲染 | 是,setState 会触发组件重新渲染。 | 否,修改 .current 属性不会触发重渲染。 |
| 主要用途 | 管理组件的声明式状态,即那些需要反映在 UI 上的数据。 | 存储不需要触发 UI 更新的数据,或用于命令式地访问 DOM。 |
| 值更新方式 | 异步更新,通过 setState 函数。 | 同步更新,直接修改 .current 属性。 |
| 何时使用 | 当数据变化需要让用户看到时。 | 当你需要一个“后台”变量,或者需要与 React 之外的世界(如 DOM)交互时。 |
总结
useRef 为我们提供了一个逃生舱,让我们可以在必要时跳出 React 纯粹的声明式数据流。它就像是函数组件的“实例属性”,为我们处理那些与渲染无关的副作用、状态和外部交互提供了强大的支持。
掌握 useRef 的这几个核心场景,可以帮助我们编写出更干净、更高效、也更灵活的 React 组件。
