react useref(长文讲解)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观
在 React 开发中,开发者常常需要直接操作 DOM 元素或存储临时数据,但 React 的状态管理机制(如 useState
)有时无法满足这些需求。这时,一个名为 useRef
的 Hook 就像一位灵活的“中间人”,在虚拟 DOM 与真实 DOM 之间架起了一座桥梁。本文将深入探讨 useRef
的核心原理、使用场景及进阶技巧,帮助开发者高效利用这一工具解决实际问题。
什么是 useRef?
useRef
是 React 提供的一个 Hook,用于创建可变的引用(reference),其核心特性是:
- 不会触发组件重渲染:修改
useRef
的值不会导致组件重新渲染; - 直接访问 DOM 元素:通过
ref
属性绑定到 DOM 元素,获取其真实引用; - 存储临时数据:适合保存不需要触发 UI 更新的临时变量。
基础用法示例
import { useRef } from "react";
function MyComponent() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current?.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
在上述代码中,useRef(null)
初始化了一个引用对象,其 .current
属性用于存储实际值。通过 ref={inputRef}
将输入框与引用绑定,即可直接调用 inputRef.current.focus()
操作 DOM。
useRef 的核心概念与类比
1. 引用的“仓库”特性
可以将 useRef
比作一个“仓库”:
- 仓库的入口(.current):
.current
是仓库的存储空间,开发者可以随时存入或取出数据; - 仓库的稳定性:仓库本身不会因数据变化而“搬迁”,即使组件多次渲染,
useRef
的引用对象始终指向同一个仓库。
这种特性使其成为存储临时数据的理想选择,例如缓存计算结果或记录组件生命周期状态。
2. 与 useState 的对比
特性 | useState | useRef |
---|---|---|
数据更新方式 | 通过 setState 触发重渲染 | 直接修改 .current 属性 |
适用场景 | 需要状态变化影响 UI | 直接操作 DOM 或存储临时值 |
例如,若需要记录用户点击次数,但无需每次点击都更新 UI,useRef
是更高效的选择:
function ClickCounter() {
const countRef = useRef(0);
const handleClick = () => {
countRef.current += 1;
console.log("Clicked:", countRef.current);
};
return <button onClick={handleClick}>Click me</button>;
}
useRef 的典型应用场景
场景 1:直接操作 DOM 元素
当需要聚焦输入框、滚动到指定位置或手动触发动画时,useRef
是最直接的解决方案。
案例:自动聚焦输入框
function AutoFocusInput() {
const inputRef = useRef();
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} type="text" />;
}
在此示例中,通过 useEffect
在组件挂载时调用 focus()
,确保输入框自动获得焦点。
场景 2:避免不必要的重渲染
某些数据(如第三方库实例或临时计算结果)不需要触发 UI 更新,此时 useRef
可避免 useState
引发的重渲染开销。
案例:缓存复杂计算结果
function MemoizedComponent({ value }) {
const cacheRef = useRef({});
if (!cacheRef.current[value]) {
cacheRef.current[value] = heavyCalculation(value);
}
return <div>{cacheRef.current[value]}</div>;
}
此处通过 useRef
缓存计算结果,避免重复执行耗时操作。
场景 3:与第三方库集成
当使用 D3.js、Three.js 等需要直接操作 DOM 的库时,useRef
可轻松获取目标元素:
function Chart() {
const chartRef = useRef();
useEffect(() => {
const chart = new SomeChartLibrary(chartRef.current);
return () => chart.destroy();
}, []);
return <div ref={chartRef} style={{ width: 400, height: 400 }} />;
}
进阶技巧与常见误区
技巧 1:结合 useEffect 管理生命周期
通过 useRef
与 useEffect
配合,可实现类似类组件 componentDidMount
和 componentWillUnmount
的逻辑:
function LifecycleExample() {
const cleanupRef = useRef();
useEffect(() => {
const subscription = someAPI.subscribe();
cleanupRef.current = () => subscription.unsubscribe();
return () => cleanupRef.current?.();
}, []);
return <div>...</div>;
}
此方法避免了在 useEffect
的依赖数组中传递函数导致的无限循环问题。
技巧 2:创建不可变的“标识符”
将 useRef
与 React.useCallback
结合,可生成唯一且稳定的标识符:
const idRef = useRef({ id: 0 });
const nextId = useCallback(() => idRef.current.id++, []);
常见误区
- 误用 useRef 替代状态管理:若数据变化需要触发 UI 更新,应优先使用
useState
或useContext
; - 忽略 .current 的初始值:直接访问
inputRef.current
可能为null
,需添加空值检查或默认值; - 过度依赖 ref 的稳定性:虽然
useRef
对象在多次渲染中保持不变,但其.current
值仍可被修改,需谨慎管理。
实战案例:动画控制与性能优化
案例:手动控制动画帧
通过 useRef
存储动画帧 ID,实现可暂停/恢复的动画:
function Animation() {
const canvasRef = useRef();
const rafIdRef = useRef();
const draw = () => {
// 绘制逻辑
rafIdRef.current = requestAnimationFrame(draw);
};
const startAnimation = () => {
if (!rafIdRef.current) {
draw();
}
};
const stopAnimation = () => {
cancelAnimationFrame(rafIdRef.current);
rafIdRef.current = undefined;
};
return (
<div>
<canvas ref={canvasRef} />
<button onClick={startAnimation}>Start</button>
<button onClick={stopAnimation}>Stop</button>
</div>
);
}
案例:优化高频 DOM 操作
在需要频繁操作 DOM 的场景中,useRef
可避免通过 document.querySelector
反复查找元素:
function ToggleElement() {
const elementRef = useRef();
const toggleVisibility = () => {
elementRef.current.style.display =
elementRef.current.style.display === "none" ? "block" : "none";
};
return (
<div>
<div ref={elementRef}>Toggle me!</div>
<button onClick={toggleVisibility}>Toggle</button>
</div>
);
}
总结
useRef
是 React 开发中不可或缺的工具,它解决了直接操作 DOM、存储临时数据及优化性能等核心问题。通过理解其“仓库”特性与使用场景,开发者可以:
- 高效管理 DOM 元素,避免冗余渲染;
- 灵活应对第三方库集成与复杂状态需求;
- 避免常见误区,写出更稳定、高效的代码。
掌握 useRef
的本质与技巧,将帮助开发者在 React 项目中实现更优雅的设计与更流畅的用户体验。
(全文约 1800 字)