React useRef 不止拿 DOM:我总结的 4 大实战场景
·
很多人以为
useRef只用来拿 DOM,其实它更像一个“不会触发重渲染的隐藏抽屉”。今天我们通过 4 个场景,让你彻底搞懂useRef的能力:操作 DOM、存储定时器、记录前一次值、配合外部库。
🧠 useRef 是什么?
const ref = useRef(initialValue);
- 返回一个
{ current: initialValue }对象。 .current可读可写,改它不会触发组件重新渲染。- 每次渲染得到的是同一个 ref 对象。
🎯 场景一:操作 DOM 元素
function AutoFocusInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} placeholder="自动聚焦" />;
}
- 类似
document.querySelector,但更安全。 - 适合做表单聚焦、滚动到元素、测量宽高。
🔁 场景二:保存定时器/外部实例
function Timer() {
const timerRef = useRef(null);
const [count, setCount] = useState(0);
const start = () => {
if (timerRef.current) return;
timerRef.current = setInterval(() => setCount((c) => c + 1), 1000);
};
const stop = () => {
clearInterval(timerRef.current);
timerRef.current = null;
};
useEffect(() => stop, []);
return (
<div>
<p>{count}</p>
<button onClick={start}>开始</button>
<button onClick={stop}>停止</button>
</div>
);
}
timerRef保存的是“跨渲染的变量”。- 不用担心
setInterval的返回值在重渲染时丢失。
🕰️ 场景三:记录上一次的值
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
function Profile({ name }) {
const prevName = usePrevious(name);
return (
<p>
当前:{name}(上一次:{prevName ?? '无'})
</p>
);
}
useRef像一个“时间胶囊”,记录上一次渲染的值。- 不会导致重新渲染,非常适合做动画、统计等对比逻辑。
🤝 场景四:关联第三方库
function Chart() {
const chartRef = useRef(null);
useEffect(() => {
if (!chartRef.current) {
chartRef.current = echarts.init(document.getElementById('chart'));
}
chartRef.current.setOption({ /* 配置项 */ });
return () => chartRef.current?.dispose();
}, []);
return <div id="chart" style={{ height: 300 }} />;
}
- 保存外部库的实例(ECharts、Mapbox、播放器)。
- 在卸载阶段释放资源,避免内存泄漏。
🧱 useRef vs useState
| 对比项 | useRef | useState |
|---|---|---|
| 修改是否触发渲染 | ❌ | ✅ |
| 适合存什么 | DOM 节点、定时器、外部实例、上一轮值 | UI 状态、需要触发更新的数据 |
| 是否稳定 | 返回同一个 ref 对象 | 每次渲染返回新的 state 值 |
⚠️ 常见坑
| 坑点 | 现象 | 解决 |
|---|---|---|
| ref.current 为空 | DOM 尚未挂载 | 在 useEffect 中访问或加条件判断 |
| 使用 ref.current 保存状态 | UI 不更新 | 把需要展示的数据存在 state |
| 父子组件传 ref | 默认不生效 | 需要 forwardRef + useImperativeHandle |
🧠 forwardRef & useImperativeHandle 示例
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
reset: () => (inputRef.current.value = ''),
}));
return <input ref={inputRef} {...props} />;
});
function App() {
const ref = useRef();
return (
<div>
<FancyInput ref={ref} />
<button onClick={() => ref.current?.focus()}>聚焦</button>
<button onClick={() => ref.current?.reset()}>清空</button>
</div>
);
}
- 让父组件获取子组件的“命令式方法”。
🏁 小练习
- 做一个“秒表”组件,用
useRef保存 start 时间,秒级更新。 - 写一个带撤销功能的输入框,把历史值放在 ref 的栈里。
下一篇我们继续扩展“性能优化工具包”——用 React.memo 和 Profiler 掌控组件渲染频率,避免“全家桶一起重渲染”的惨剧。
更多推荐


所有评论(0)