React Hook: useRef 详解
·
React Hook: useRef 详解
什么是 useRef?
useRef 是一个 React Hook,它返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数。useRef 的主要特点是:
- 不会触发重新渲染:修改
ref.current的值不会导致组件重新渲染 - 在组件重新渲染之间保持值:ref 对象在组件的整个生命周期中保持不变
- 可以访问 DOM 元素:通过 ref 可以直接操作 DOM
基本语法
const ref = useRef(initialValue);
// 访问值
ref.current
// 修改值
ref.current = newValue;
主要用途
1. 访问 DOM 元素
最常见的用法是获取 DOM 元素的引用,以便直接操作 DOM(如聚焦、滚动、测量尺寸等)。
import { useRef, useEffect } from 'react';
function AutoFocusInput() {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
// 组件挂载后自动聚焦
inputRef.current?.focus();
}, []);
return <input ref={inputRef} type="text" />;
}
关键点:
- 使用
ref={inputRef}将 ref 附加到 JSX 元素 - 通过
inputRef.current访问实际的 DOM 元素 - 使用可选链
?.避免 null 引用错误
2. 保存可变值(不触发重新渲染)
当你需要一个值在组件重新渲染之间保持不变,但又不想触发重新渲染时,可以使用 useRef。
function RefCounter() {
const countRef = useRef(0);
const [renderCount, setRenderCount] = useState(0);
const increment = () => {
countRef.current += 1;
console.log('当前计数:', countRef.current);
// 注意:这不会触发重新渲染!
// 如果需要显示变化,需要更新 state
setRenderCount(prev => prev + 1);
};
return (
<div>
<p>Ref 计数: {countRef.current}</p>
<button onClick={increment}>增加</button>
</div>
);
}
与 useState 的区别:
useState:修改值会触发重新渲染useRef:修改值不会触发重新渲染
3. 保存前一个值
使用 useRef 和 useEffect 可以轻松保存前一个值:
function PreviousValue() {
const [count, setCount] = useState(0);
const prevCountRef = useRef<number>();
useEffect(() => {
// 在渲染后保存当前值作为"前一个值"
prevCountRef.current = count;
}, [count]);
return (
<div>
<p>当前值: {count}</p>
<p>前一个值: {prevCountRef.current ?? '无'}</p>
</div>
);
}
4. 保存定时器 ID 或其他需要清理的资源
function TimerComponent() {
const [seconds, setSeconds] = useState(0);
const timerRef = useRef<NodeJS.Timeout | null>(null);
const startTimer = () => {
// 清除之前的定时器(如果存在)
if (timerRef.current) {
clearInterval(timerRef.current);
}
// 保存新的定时器 ID
timerRef.current = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
};
const stopTimer = () => {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
};
// 组件卸载时清理
useEffect(() => {
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
}, []);
return (
<div>
<p>计时: {seconds} 秒</p>
<button onClick={startTimer}>开始</button>
<button onClick={stopTimer}>停止</button>
</div>
);
}
为什么使用 ref 而不是 state?
- 定时器 ID 不需要在 UI 中显示
- 不需要因为定时器 ID 的变化而触发重新渲染
- 只需要在需要清理时能够访问到它
useRef vs useState
| 特性 | useRef | useState |
|---|---|---|
| 触发重新渲染 | ❌ 否 | ✅ 是 |
| 值在渲染间保持 | ✅ 是 | ✅ 是 |
| 可以访问 DOM | ✅ 是 | ❌ 否 |
| 适合存储 UI 状态 | ❌ 否 | ✅ 是 |
| 适合存储不需要显示的值 | ✅ 是 | ❌ 否 |
常见使用场景
✅ 使用 useRef 的场景:
-
访问 DOM 元素
- 聚焦输入框
- 滚动到特定位置
- 测量元素尺寸
- 触发动画
-
保存不需要触发渲染的值
- 定时器 ID
- 订阅 ID
- 前一个 props/state 的值
- 缓存计算结果
-
存储可变对象引用
- 避免在 useEffect 依赖数组中包含对象
❌ 不应该使用 useRef 的场景:
- 需要触发重新渲染的值 → 使用
useState - 需要在 JSX 中显示的值 → 使用
useState - 需要计算的值 → 使用
useMemo或useState
完整示例:滚动到元素
import { useRef } from 'react';
function ScrollToElement() {
const targetRef = useRef<HTMLDivElement>(null);
const scrollToTarget = () => {
targetRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
};
return (
<div>
<button onClick={scrollToTarget}>滚动到目标</button>
<div style={{ height: '1000px' }}>
{/* 很多内容 */}
</div>
<div ref={targetRef} style={{ background: 'yellow' }}>
<h2>目标元素</h2>
</div>
</div>
);
}
注意事项
-
不要在渲染期间修改 ref.current
// ❌ 错误:在渲染期间修改 function BadComponent() { const ref = useRef(0); ref.current = 1; // 不要这样做! return <div>{ref.current}</div>; } // ✅ 正确:在事件处理或 useEffect 中修改 function GoodComponent() { const ref = useRef(0); const handleClick = () => { ref.current = 1; // 可以 }; return <button onClick={handleClick}>点击</button>; } -
ref.current 可能是 null
- 在组件首次渲染时,如果 ref 附加到 DOM 元素,
ref.current可能是null - 使用可选链
?.来安全访问
- 在组件首次渲染时,如果 ref 附加到 DOM 元素,
-
ref 不会自动清理
- 如果 ref 存储的是需要清理的资源(如定时器、订阅),记得在
useEffect的清理函数中清理
- 如果 ref 存储的是需要清理的资源(如定时器、订阅),记得在
关键要点
useRef返回的对象在组件整个生命周期中保持不变- 修改
ref.current不会触发重新渲染 - 主要用于访问 DOM 和存储不需要触发渲染的值
- 使用
ref={myRef}将 ref 附加到 JSX 元素 - 通过
myRef.current访问值或 DOM 元素 - 总是使用可选链
?.来安全访问可能为 null 的 ref
实际应用示例
查看 App.tsx 文件中的以下组件来了解实际用法:
AutoFocusInput- 自动聚焦输入框RefCounter- 使用 ref 存储计数器(不触发渲染)PreviousValue- 保存前一个值TimerComponent- 保存定时器 IDScrollToElement- 滚动到指定元素
更多推荐


所有评论(0)