React 中 useRef 除了引用 DOM 还能做什么?

📅 2026/7/30 10:17:44 👁️ 阅读次数 📝 编程学习
React 中 useRef 除了引用 DOM 还能做什么?

React 中 useRef 除了引用 DOM 还能做什么?

一句话总结:useRef 是一个「渲染间持久化的可变容器」,除了存 DOM 节点,还能存定时器 ID、上一次的值、跨渲染的标志位,是函数组件里不触发重渲染的「私有变量」!


正文目录

  1. useRef 到底是什么?
  2. 5 大高频使用场景 & 代码示例
  3. 万能兜底:什么时候用 useRef vs useState
  4. 预防 checklist(不再踩坑)
  5. 一句话总结

一、useRef 到底是什么?

const ref = useRef(initialValue); // ref.current = initialValue(初始值) // 修改 ref.current 不会触发重渲染 // 组件每次渲染,ref 是同一个对象

核心特性

特性useRefuseState
修改后触发重渲染?❌ 不触发✅ 触发
跨渲染保持值?✅ 是✅ 是
修改方式ref.current = valsetState(val)
适合存什么DOM、定时器、标志位需要展示的 UI 数据

一句话:需要页面响应变化 → useState;只需要存个值但不影响 UI → useRef。


二、5 大高频使用场景 & 代码示例

① 引用 DOM 节点(最常见)
import { useRef, useEffect } from 'react'; function InputFocus() { const inputRef = useRef(null); useEffect(() => { inputRef.current?.focus(); // ✅ 挂载后自动聚焦 }, []); return <input ref={inputRef} />; }
② 存储定时器 ID,避免泄漏
// ❌ 定时器存在普通变量里,每次渲染都会重置 function Timer() { let timerId = null; // ❌ 每次渲染都是 null useEffect(() => { timerId = setInterval(() => console.log('tick'), 1000); return () => clearInterval(timerId); // ❌ timerId 可能是 null }, []); }

修复:用 useRef 存定时器

// ✅ useRef 存定时器 ID,跨渲染稳定 import { useRef, useEffect } from 'react'; function Timer() { const timerRef = useRef(null); useEffect(() => { timerRef.current = setInterval(() => { console.log('tick'); }, 1000); return () => clearInterval(timerRef.current); // ✅ 清理时能拿到 }, []); const stop = () => clearInterval(timerRef.current); return <button onClick={stop}>停止</button>; }
③ 记录上一次的值(prevValue)
// ✅ 用 useRef 记录上一次的 props/state import { useRef, useEffect } from 'react'; function PriceDisplay({ price }) { const prevPriceRef = useRef(price); useEffect(() => { prevPriceRef.current = price; // ✅ 渲染后更新 }); const isUp = price > prevPriceRef.current; return ( <div style={{ color: isUp ? 'red' : 'green' }}> ¥{price} {isUp ? '↑' : '↓'} </div> ); }
④ 防止 useEffect 里的闭包陷阱
// ❌ useEffect 空依赖,但里面用了最新的 state → 闭包旧值 function Counter() { const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { console.log(count); // ❌ 永远是 0 setCount(count + 1); // ❌ 永远设成 1 }, 1000); return () => clearInterval(timer); }, []); }

修复方案一:useRef 存最新值

// ✅ useRef 存最新 count,定时器里读 ref.current function Counter() { const [count, setCount] = useState(0); const countRef = useRef(count); useEffect(() => { countRef.current = count; // ✅ 每次渲染更新 ref }); useEffect(() => { const timer = setInterval(() => { setCount(countRef.current + 1); // ✅ 读最新值 }, 1000); return () => clearInterval(timer); }, []); return <div>{count}</div>; }

修复方案二:函数式更新(更简洁)

// ✅ 函数式更新,不需要 ref function Counter() { const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { setCount(prev => prev + 1); // ✅ 拿到最新值 }, 1000); return () => clearInterval(timer); }, []); return <div>{count}</div>; }
⑤ 标志位:控制「只执行一次」或「是否已初始化」
// ✅ 用 useRef 做初始化标志,防止重复执行 import { useRef, useEffect } from 'react'; function useInitOnce(callback) { const initializedRef = useRef(false); useEffect(() => { if (initializedRef.current) return; // ✅ 只执行一次 initializedRef.current = true; callback(); }, [callback]); }
// ✅ 防抖:用 useRef 存防抖定时器 function SearchInput() { const [value, setValue] = useState(''); const debounceRef = useRef(null); const handleChange = (e) => { const val = e.target.value; setValue(val); clearTimeout(debounceRef.current); // ✅ 清掉上一个定时器 debounceRef.current = setTimeout(() => { fetchResults(val); // ✅ 500ms 后发请求 }, 500); }; useEffect(() => { return () => clearTimeout(debounceRef.current); // ✅ 卸载时清理 }, []); return <input value={value} onChange={handleChange} />; }

三、万能兜底:什么时候用 useRef vs useState

决策流程

这个值变化后,UI 需要更新吗? ├── 是 → useState └── 否 → 这个值需要在事件/定时器里访问吗? ├── 是 → useRef └── 否 → 普通变量就行
场景用什么原因
输入框内容useStateUI 需要响应
加载状态useStateUI 需要响应
DOM 节点引用useRef不需要触发渲染
定时器 IDuseRef不需要触发渲染
上一次的值useRef用于比较,不直接渲染
防抖定时器useRef不需要触发渲染
初始化标志useRef只控制逻辑,不渲染
isMounted 标志useRef防止卸载后 setState

万能模板

import { useState, useRef, useEffect } from 'react'; function useDebounce(callback, delay) { const timerRef = useRef(null); // ✅ 存定时器 const callbackRef = useRef(callback); // ✅ 存最新 callback useEffect(() => { callbackRef.current = callback; // ✅ 每次渲染更新 }); useEffect(() => { return () => clearTimeout(timerRef.current); // ✅ 卸载清理 }, []); const debounced = (...args) => { clearTimeout(timerRef.current); timerRef.current = setTimeout(() => { callbackRef.current(...args); // ✅ 调最新的 }, delay); }; return debounced; }

四、预防 checklist

  • 需要 UI 响应 → useState,不需要 → useRef
  • 定时器、事件监听器的 ID用 useRef 存,useEffect cleanup 里清理
  • 防抖/节流用 useRef 存定时器,别用普通变量(每次渲染重置)
  • 闭包陷阱优先用函数式更新解决,实在不行再上 useRef
  • 记录「上一次的值」用 useRef + useEffect,在渲染后更新
  • 不要在渲染过程中写 ref.current(除初始化),只在事件回调和 effect 里写
  • ref.current 读写在事件处理和 effect 里是安全的,不要在组件渲染阶段依赖它
  • useRef 初始值useRef(null)给 DOM,useRef(0)给计数器,useRef(false)给标志位

五、一句话总结

「useRef = 不触发重渲染的持久化容器」。DOM 引用只是最基础的用法,定时器 ID、上一次的值、初始化标志、防抖定时器都是它的主场。记住:UI 要变用 useState,逻辑要存不变用 useRef!


最后问候亲爱的朋友们,并邀请你们阅读我的全新著作

📚 《React 进阶实战》