当前位置: 首页 > news >正文

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 进阶实战》

http://www.jsqmd.com/news/1293120/

相关文章:

  • c++入门教程7:增删改查
  • 番茄小说下载器终极指南:5步轻松实现高质量离线阅读
  • 量子隐形传态原理与实践:从量子纠缠到安全通信的实现
  • 黄陂南德国际城二期周边 九分饱烧烤大排档下楼就到实测 - 热点速览
  • 2026 年呼和浩特旧房改造水电维修实测,老房翻新避坑实用经验 - LYL仔仔
  • 2026武昌旧房改造厨房翻新,口碑橱柜定制避坑指南 - 热点速览
  • 2026年乐清开荒保洁该怎么挑选?帮馨家政18868283137甄选参考指南 - GrowthUME
  • Netty 百万长连接推送网关生产实战
  • APA102C LED驱动芯片:从双线协议到工程实践全解析
  • Kimi K3幻灯片生成模型评测:本地部署与API集成实践指南
  • Noiz Easter Voice:AI语音生成的趣味彩蛋玩法解析
  • 字符串处理全攻略:从基础操作到高级算法实战
  • 终极指南:DockDoor - 为macOS带来Windows式高效窗口管理的免费神器
  • AI生成产品展示图:2024最严审核季来临,这4类“看似完美”的AI图已被批量下架——附官方申诉话术库(含中英双语)
  • 2026年东营靠谱窗帘店推荐,金大地希布不套路 - 热点速览
  • Java类设计核心关键字解析与最佳实践
  • 2026培训机构教务管理系统实测对比,多款主流软件全面测评 - 品牌测评鉴赏家
  • 2026年7月探秘!东莞这些大型JDG线管生产工厂你知道几个? - 热点速览
  • 企业新媒体代运营服务选择与评估全指南
  • Seraphine:英雄联盟玩家的系统性数据助手
  • 2026年钛合金零件加工?这些好用的服务商你知道吗? - 热点速览
  • ML.NET 核心技术精讲:特征工程 + 算法选型 + 部署优化
  • Python爬虫实战:抓取天天基金排行榜数据,构建本地基金数据库
  • 2026年采选冶化废水处理/酿造发酵废水处理哪家性价比高:五家具备工程落地能力的服务商综合参考 - 深度智识库
  • 2026年HRSaaS行业AI技术应用与选型指南
  • 海思Hi3531D平台HDMI视频采集:IT6801桥接芯片驱动与BT.1120接口调试实战
  • 2026年银川正规非急救救护车转运服务联系电话是多少 - 热点速览
  • 制造业AI落地的“东莞样本”有了哪些新进展?
  • 千眼智推一体化交付,告别运营、技术多方对接内耗 - 热点速览
  • 游戏角色技能体系设计:从逻辑构建到工程落地的实践指南