hook:useRef
- 用来记录一个值,可用
.current属性取出 - 核心特点: 改变
ref.current不会触发组件重新渲染,引用在整个组件生命周期内保持不变
场景代码示例
-
【场景 1】引用 DOM 元素(最常用):
-
获取 DOM 元素的引用,用于操作 DOM
// 场景:聚焦输入框 - 通过 ref 获取 DOM 元素 function TextInputWithFocusButton() { const inputEl = useRef(null); // 初始值为 null const onButtonClick = () => { inputEl.current.focus(); // 通过 .current 访问 DOM 元素 }; return ( <div> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </div> ); } -
inputEl.current会被自动赋值为对应的 DOM 元素 -
组件挂载后,
inputEl.current指向实际的<input>节点 -
可用于:聚焦、滚动、测量尺寸、播放视频等 DOM 操作
-
-
【场景 2】存储定时器 ID(常用):
-
保存定时器 ID,用于清除定时器
// 场景:清除定时器 - ref 存储 timer ID function Timer() { const [count, setCount] = useState(0); const timerRef = useRef(null); const startTimer = () => { if (timerRef.current) return; // 防止重复启动 timerRef.current = setInterval(() => { setCount((c) => c + 1); }, 1000); }; const stopTimer = () => { clearInterval(timerRef.current); timerRef.current = null; }; useEffect(() => { return () => clearInterval(timerRef.current); // 组件卸载时清除 }, []); return ( <div> <p>Count: {count}</p> <button onClick={startTimer}>Start</button> <button onClick={stopTimer}>Stop</button> </div> ); } -
如果用 state 存储 timer ID,每次 setState 会触发重新渲染
-
用 ref 存储,修改
timerRef.current不会触发渲染
-
-
【场景 3】存储前一次的值(常用):
-
保存上一次渲染时的某个值,用于对比
// 场景:追踪前值 - 记录上一次的 count function Counter() { const [count, setCount] = useState(0); const prevCountRef = useRef(); useEffect(() => { prevCountRef.current = count; // 每次渲染后更新 }); const prevCount = prevCountRef.current; // 取出上一次的值 return ( <div> <p>当前: {count}</p> <p>之前: {prevCount}</p> <button onClick={() => setCount(count + 1)}>+1</button> </div> ); } -
初次渲染时,
prevCount为undefined -
后续渲染时,
prevCount保存的是上一次的count值 -
可用于:检测值是否变化、计算差值等
-
-
【场景 4】存储不需要触发渲染的可变值(常用):
-
存储任何需要在渲染之间保持的值,但不需要触发重新渲染
// 场景:记录点击次数 - 不需要显示,只用于统计 function ClickTracker() { const clickCountRef = useRef(0); const [message, setMessage] = useState(""); const handleClick = () => { clickCountRef.current += 1; // 修改 ref 不触发渲染 console.log("Total clicks:", clickCountRef.current); if (clickCountRef.current >= 10) { setMessage("你已经点击了 10 次!"); // 触发渲染 } }; return ( <div> <button onClick={handleClick}>点击我</button> <p>{message}</p> </div> ); } -
适用于不需要在 UI 中显示的数据
-
类似于类组件中的实例变量
this.xxx
-
-
【场景 5】避免闭包陷阱(进阶):
-
在回调函数中访问最新的状态值
// 场景:获取最新的 count 值 - 避免闭包陷阱 function Counter() { const [count, setCount] = useState(0); const countRef = useRef(count); // 每次 count 更新时,同步更新 ref useEffect(() => { countRef.current = count; }, [count]); const handleClick = () => { setTimeout(() => { // 3 秒后访问最新的 count alert("最新的 count: " + countRef.current); }, 3000); }; return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>+1</button> <button onClick={handleClick}>3秒后查看最新值</button> </div> ); } -
如果直接在 setTimeout 中访问
count,会形成闭包,拿到的是旧值 -
使用
countRef.current可以始终获取最新值
-
useRef vs useState
| 特性 | useRef | useState |
|---|---|---|
| 修改是否触发渲染 | ❌ 不触发 | ✅ 触发 |
| 值的访问方式 | ref.current | 直接访问 |
| 渲染间是否保持 | ✅ 保持(引用不变) | ✅ 保持(值不变) |
| 主要用途 | 存储不需要显示的可变值、DOM 引用 | 存储需要显示的状态 |
| 更新时机 | 立即同步更新 | 异步批量更新 |
选择原则:
- 需要在 UI 中显示 → 用
useState - 不需要在 UI 中显示 → 用
useRef
常见应用场景总结
| 场景 | 说明 | 示例 |
|---|---|---|
| 引用 DOM 元素 | 获取 DOM 节点,进行操作 | 聚焦输入框、滚动、测量尺寸 |
| 存储定时器 ID | 保存 timer/interval ID,便于清除 | setTimeout、setInterval |
| 存储前一次的值 | 对比前后值的变化 | 检测变化、计算差值 |
| 存储可变值 | 不需要触发渲染的数据 | 统计数据、缓存、标志位 |
| 避免闭包陷阱 | 在异步回调中获取最新值 | setTimeout、事件监听器 |
| 存储函数实例 | 存储不随渲染变化的函数引用 | 第三方库实例、事件处理器 |
注意事项
-
⚠️ 不要在渲染期间读写
ref.current,这会导致组件行为不一致// ❌ 错误:渲染期间修改 ref function Bad() { const ref = useRef(0); ref.current += 1; // 不要这样做! return <div>{ref.current}</div>; } // ✅ 正确:在事件处理或 effect 中修改 function Good() { const ref = useRef(0); useEffect(() => { ref.current += 1; // 在 effect 中修改 }); return <div>Good</div>; } -
⚠️ 修改
ref.current不会触发重新渲染// ❌ 不会更新 UI const countRef = useRef(0); const increment = () => { countRef.current += 1; // UI 不会更新 }; // ✅ 需要显示在 UI 中,用 useState const [count, setCount] = useState(0); const increment = () => { setCount(count + 1); // UI 会更新 }; -
✅
ref在整个组件生命周期内保持同一个引用,适合存储跨渲染的可变值