一、useRef 是什么?
useRef 是 React 提供的一个内置钩子(Hook),核心作用是创建一个可以在组件整个生命周期内持久化的可变容器(ref 对象)。
这个容器有一个唯一的属性 .current,你可以把任意类型的值(比如 DOM 元素、普通变量、对象等)存到 .current 里,且:
- 修改
.current的值不会触发组件重新渲染; - 组件重新渲染时,
useRef返回的 ref 对象始终是同一个(不会重新创建); - 这个值会在组件挂载、更新、卸载的整个生命周期中保留,不会像普通变量一样在每次渲染时重置。
基础用法示例:
jsx
import { useRef } from 'react';
function MyComponent() {
// 创建一个 ref 对象,初始值为 0
const countRef = useRef(0);
const handleClick = () => {
// 修改 .current 不会触发组件重渲染
countRef.current += 1;
console.log('点击次数:', countRef.current); // 能正确打印,但页面不会更新
};
return (
<button onClick={handleClick}>
点击(控制台看次数)
</button>
);
}
二、useRef vs useState 核心区别
这是新手最容易混淆的点,核心差异在于是否触发重渲染和值的更新逻辑,具体对比如下:
| 特性 | useRef | useState |
|---|---|---|
| 触发重渲染 | 修改 .current 不会触发重渲染 | 调用 setState 会触发组件重渲染 |
| 值的持久性 | 生命周期内始终是同一个 ref 对象 | 状态值持久化,但每次 setState 会生成新值 |
| 取值/改值方式 | 直接读写 .current(无限制) | 读:直接取状态变量;改:必须通过 setState |
| 适用场景 | 存储无需渲染的可变值、获取DOM | 存储需要驱动视图更新的状态 |
对比示例:
jsx
import { useRef, useState } from 'react';
function CompareDemo() {
const [countState, setCountState] = useState(0);
const countRef = useRef(0);
// 点击更新 useState:页面会刷新
const handleStateClick = () => {
setCountState(prev => prev + 1);
};
// 点击更新 useRef:页面不刷新,仅控制台打印
const handleRefClick = () => {
countRef.current += 1;
console.log('ref 计数:', countRef.current);
};
return (
<div>
<p>useState 计数:{countState}</p>
<button onClick={handleStateClick}>更新 useState</button>
<button onClick={handleRefClick}>更新 useRef</button>
</div>
);
}
三、useRef 的主要用途
1. 获取/操作 DOM 元素(最常用)
通过 ref 属性绑定 DOM 元素,就能在组件中直接访问并操作该 DOM(比如聚焦输入框、获取元素尺寸等)。
jsx
import { useRef, useEffect } from 'react';
function InputFocus() {
// 创建 ref 绑定到 input 元素
const inputRef = useRef(null);
useEffect(() => {
// 组件挂载后,让 input 自动聚焦
inputRef.current.focus();
}, []);
return (
<input
ref={inputRef}
type="text"
placeholder="自动聚焦的输入框"
/>
);
}
2. 存储无需触发渲染的可变值
比如记录组件渲染次数、定时器 ID、上一次的状态值等,这些值不需要展示在页面上,修改后也不需要触发重渲染。
jsx
import { useRef, useEffect, useState } from 'react';
function RenderCounter() {
const [count, setCount] = useState(0);
// 存储渲染次数(无需驱动视图)
const renderCountRef = useRef(0);
// 存储上一次的 count 值
const prevCountRef = useRef(0);
useEffect(() => {
// 每次渲染后,更新渲染次数
renderCountRef.current += 1;
// 每次 count 变化后,更新上一次的值
prevCountRef.current = count;
});
return (
<div>
<p>当前 count:{count}</p>
<p>上一次 count:{prevCountRef.current}</p>
<p>组件渲染次数:{renderCountRef.current}</p>
<button onClick={() => setCount(prev => prev + 1)}>加 1</button>
</div>
);
}
3. 保存定时器/订阅等清理逻辑的引用
组件卸载时需要清理定时器、取消订阅,用 useRef 存储定时器 ID,能在任意时机访问到这个 ID。
jsx
import { useRef, useEffect, useState } from 'react';
function TimerDemo() {
const [time, setTime] = useState(0);
// 存储定时器 ID
const timerRef = useRef(null);
useEffect(() => {
// 启动定时器
timerRef.current = setInterval(() => {
setTime(prev => prev + 1);
}, 1000);
// 组件卸载时清理定时器
return () => {
clearInterval(timerRef.current);
};
}, []);
return (
<div>
<p>计时:{time} 秒</p>
<button onClick={() => clearInterval(timerRef.current)}>暂停</button>
</div>
);
}
总结
useRef是创建生命周期内持久化的可变容器,修改.current不触发重渲染;useState用于存储驱动视图更新的状态,修改会触发重渲染。useRef核心用途:① 获取/操作 DOM 元素;② 存储无需渲染的可变值(如渲染次数、定时器 ID);③ 保存上一次的状态/值。- 选择原则:需要更新后刷新页面 → 用
useState;仅需持久化存储、无需刷新页面 → 用useRef。