React 19 入门教程
useRef 引用值
本教程共 50 篇 · 第 30 篇 · 更新于 2026-07-29 · 约 6 分钟阅读
ReactuseRefref可变值不触发渲染
30. useRef 引用值
本节目标:理解 ref 是一种”不触发渲染的状态”,掌握 useRef 的用法,知道什么时候该用 ref 而不是 state。
ref 是什么
useRef 返回一个对象 { current: 初始值 }。你可以随时读写 ref.current。
import { useRef } from 'react';
function Demo() {
const ref = useRef(0);
function handleClick() {
ref.current = ref.current + 1;
console.log(ref.current); // 数字在变
}
return <button onClick={handleClick}>点击</button>;
}
点击按钮,ref.current 递增,但页面不会更新。因为修改 ref 不触发重新渲染。
ref 是 React 追踪不到的”口袋”
React 的 state 变化会触发渲染。ref 变化完全静悄悄,React 不知道,也不关心。
ref vs state
| ref | state | |
|---|---|---|
| 返回值 | { current: value } | [value, setValue] |
| 修改方式 | ref.current = newVal | setValue(newVal) |
| 修改后 | 不触发渲染 | 触发渲染 |
| 读取时机 | 不要在渲染期间读 | 随时读 |
| 值的可变性 | 可变(直接改) | 不可变(必须 setState) |
// state 版本:每次点击页面更新
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
// ref 版本:数字变了但页面不更新
function Counter() {
const count = useRef(0);
return (
<button onClick={() => { count.current += 1; console.log(count.current); }}>
点击(看控制台)
</button>
);
}
什么时候用 ref
存储定时器 ID
function Timer() {
const [count, setCount] = useState(0);
const intervalRef = useRef(null);
function start() {
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
}
function stop() {
clearInterval(intervalRef.current);
}
return (
<>
<p>{count}</p>
<button onClick={start}>开始</button>
<button onClick={stop}>停止</button>
</>
);
}
定时器 ID 不需要渲染,用 ref 存刚好。
记录上一次的值
function Chat({ message }) {
const prevMessageRef = useRef();
useEffect(() => {
prevMessageRef.current = message;
}, [message]);
const prevMessage = prevMessageRef.current;
return (
<div>
<p>当前:{message}</p>
<p>上一条:{prevMessage}</p>
</div>
);
}
记录渲染次数
function RenderCount() {
const count = useRef(0);
count.current += 1;
return <p>渲染了 {count.current} 次</p>;
}
ref 的注意事项
不要在渲染期间修改 ref
function Bad() {
const ref = useRef(0);
ref.current += 1; // 危险!渲染期间修改
return <p>{ref.current}</p>;
}
渲染期间修改 ref 会导致不可预测的行为。在事件处理函数或 effect 里修改。
不要在渲染期间读取 ref
function Bad() {
const ref = useRef(0);
return <p>{ref.current}</p>; // 每次渲染可能不同
}
渲染期间读取 ref 会让 UI 不稳定。需要显示的值用 state。
ref 的核心原则
ref 用于存储”不影响渲染的数据”。如果需要显示在页面上,用 state。
ref 的初始值
// 初始值为 null(常用于 DOM 引用)
const ref = useRef(null);
// 初始值为数字
const count = useRef(0);
// 初始值为对象
const data = useRef({ x: 0, y: 0 });
// 惰性初始值(复杂计算只执行一次)
const ref = useRef(() => expensiveCalc());
// 注意:这样存的是函数,不是计算结果
惰性初始值的正确写法
要惰性计算初始值,用
useRef配合条件判断,或者直接用useState的函数形式。
实战:秒表
function Stopwatch() {
const [now, setNow] = useState(null);
const [startTime, setStartTime] = useState(null);
const intervalRef = useRef(null);
function handleStart() {
setStartTime(Date.now());
setNow(Date.now());
clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
setNow(Date.now());
}, 10);
}
function handleStop() {
clearInterval(intervalRef.current);
}
let seconds = 0;
if (startTime != null && now != null) {
seconds = (now - startTime) / 1000;
}
return (
<>
<p>{seconds.toFixed(3)} 秒</p>
<button onClick={handleStart}>开始</button>
<button onClick={handleStop}>停止</button>
</>
);
}
startTime 和 now 用 state(需要渲染),intervalRef 用 ref(不需要渲染)。
本节要点
- ref 是不触发渲染的可变值
- 用于存储定时器 ID、DOM 引用、上一次的值
- 渲染期间不读不写 ref
- 需要显示的用 state,不需要显示的用 ref