Table of Contents

1、概述

useRef 是 另外一个用于声明变量的内置 Hook,与 useState 不同的是,useRef 声明的变量不会触发组件重新渲染

它的基础语法如下

index.tsx
01
import { useRef } from 'react'
02
03
function MyComponent() {
04
// 声明一个 ref 变量
05
const ref = useRef(0)
06
07
function clickHandler() {
08
// 通过 ref.current 访问变量的最新值
09
ref.current += 1
10
}
11
12
...
13
}

useRef() 接收一个参数, 这个参数是初始值, 可以是任何值, 也可以是函数. 其执行之后,返回一个仅包含一个 .current 属性的对象

index.tsx
1
const ref = useRef(0)
2
3
console.log(ref.current) // 0

如果你是在 ts 中使用,通常我们无需额外为 useRef 声明的变量,添加类型声明,ts 会根据你的默认值自动推导类型,当然,你也可以通过泛型来显式声明类型

index.tsx
1
const ref = useRef<number>(0)
2
3
console.log(ref.current) // 0

2、与 useState 的区别

通过 useRef 声明的变量值发生变化时,不会触发组件重新渲染

而通过 useState 声明的变量值发生变化时,会触发组件重新渲染

我们通过如下的案例,来演示两者的区别

预览

useRef 与 useState 的区别

观察变量如何在不触发 UI 渲染的情况下悄悄改变

UI 渲染次数
0
Ref 存储值
0
  • 点击第一个按钮:Ref 在控制台增加,但右侧数字不变。
  • 点击第二个按钮:组件刷新,Ref 的最新值被“揭晓”。
  • 结论:useRef 是完美的“幕后英雄”,适合存储不需要立即反馈到界面的数据。
index.tsx
01
import React, { useState, useRef } from 'react';
02
03
const RefDemo = () => {
04
// 用于触发 UI 渲染的状态
05
const [renderCount, setRenderCount] = useState(0);
06
07
// useRef 存储的变量:改变它不会导致 UI 更新
08
const clickTracker = useRef(0);
09
10
const handleRefClick = () => {
11
clickTracker.current += 1;
12
console.log('Ref 值已改变,但 UI 没动:', clickTracker.current);
13
};
14
15
const forceRender = () => {
16
setRenderCount(prev => prev + 1);
17
};
18
19
return (
20
<div className="bg-slate-900 flex items-center justify-center p-6">
21
<div className="max-w-md w-full bg-slate-800 border border-slate-700 p-8 shadow-2xl">
22
23
{/* 标题区域 */}
24
<div className="mb-8">
25
<h2 className="text-2xl font-bold text-white mb-2">useRef 与 useState 的区别</h2>
26
<p className="text-slate-400! text-sm">观察变量如何在不触发 UI 渲染的情况下悄悄改变</p>
27
</div>
28
29
{/* 核心数据展示 */}
30
<div className="grid grid-cols-2 gap-4 mb-8">
31
<div className="bg-slate-900/50 p-4 border border-slate-700/50">
32
<span className="text-slate-500 text-xs uppercase tracking-wider font-semibold">UI 渲染次数</span>
33
<div className="text-3xl font-mono font-bold text-cyan-400">{renderCount}</div>
34
</div>
35
<div className="bg-slate-900/50 p-4 border border-slate-700/50">
36
<span className="text-slate-500 text-xs uppercase tracking-wider font-semibold">Ref 存储值</span>
37
<div className="text-3xl font-mono font-bold text-emerald-400">{clickTracker.current}</div>
38
</div>
39
</div>
40
41
{/* 操作按钮 */}
42
<div className="space-y-4">
43
<button
44
onClick={handleRefClick}
45
className="w-full py-4 bg-emerald-500/10 hover:bg-emerald-500/20 border border-emerald-500/50 text-emerald-400 font-medium transition-all active:scale-[0.98] flex items-center justify-center gap-2"
46
>
47
<span className="w-2 h-2 bg-emerald-400 rounded-full animate-pulse"></span>
48
秘密增加 Ref (静默)
49
</button>
50
51
<button
52
onClick={forceRender}
53
className="w-full py-4 bg-cyan-500 hover:bg-cyan-600 text-white font-medium shadow-lg shadow-cyan-500/20 transition-all active:scale-[0.98]"
54
>
55
触发重新渲染 (同步 UI)
56
</button>
57
</div>
58
59
<div className="mt-8 pt-6 border-t border-slate-700">
60
<ul className="space-y-2 italic">
61
<li className="text-slate-400! text-xs">点击第一个按钮:Ref 在控制台增加,但右侧数字不变。</li>
62
<li className="text-slate-400! text-xs">点击第二个按钮:组件刷新,Ref 的最新值被“揭晓”。</li>
63
<li className="text-slate-400! text-xs">结论:useRef 是完美的“幕后英雄”,适合存储不需要立即反馈到界面的数据。</li>
64
</ul>
65
</div>
66
</div>
67
</div>
68
);
69
};
70
71
export default RefDemo;

从另一个角度的上来说,当组件由于 state 值的变化而重新渲染时,useRef 声明的变量值不会发生变化.

  • 静默性useRef 通过 .current 修改值时,不会触发组件重新渲染
  • 持久性useRef 声明的变量值不会随着组件的重新渲染而被重置

基于这个特性,我们可以发掘出来一些非常有意思的用法

3、保存真实 DOM 元素的引用

DOM 封装的元素,例如 inputtextareaselect 等,我们可以通过他们的属性 ref 来获取其真实 DOM 的引用

通常的做法是,我们会将该引用保存到 useRef 中,因为真实 DOM 一旦创建,就会一直存在,其引用需要稳定性

如下案例所示,我们可以通过点击按钮,来获取输入框的焦点

预览

Quick Action

准备好开始
你的学习了吗?

点击卡片任意位置或按钮,
底层使用 useRef.current.focus() 实现

index.tsx
01
import { useRef } from 'react';
02
import { Focus } from 'lucide-react'
03
04
export default function MagicFocusDemo() {
05
const inputElement = useRef<HTMLInputElement>(null);
06
07
const handleActivate = () => {
08
if (inputElement.current) {
09
inputElement.current.focus();
10
}
11
};
12
13
return (
14
<div className="flex items-center justify-center p-4">
15
<div className="max-w-md w-full">
16
{/* 卡片容器 */}
17
<div
18
onClick={handleActivate}
19
className="group relative bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 p-8 cursor-pointer"
20
>
21
<div className="relative z-10">
22
<h3 className="text-sm font-semibold text-indigo-600 dark:text-indigo-400 mb-2 tracking-widest uppercase">Quick Action</h3>
23
<h2 className="text-3xl font-bold text-neutral-800 dark:text-neutral-200 mb-6 tracking-tight">
24
准备好开始<br />你的学习了吗?
25
</h2>
26
27
<div className="relative">
28
<input
29
ref={inputElement}
30
type="text"
31
placeholder="输入项目名称..."
32
className="w-full bg-neutral-100 dark:bg-neutral-800 border-none focus:ring-2 focus:ring-indigo-500/20 px-5 py-4 text-neutral-700 dark:text-neutral-300 placeholder:text-neutral-400 dark:placeholder:text-neutral-600 transition-all duration-300 outline-none"
33
/>
34
35
{/* 装饰性搜索图标 */}
36
<div className="absolute right-4 top-1/2 -translate-y-1/2 text-neutral-400 dark:text-neutral-600 group-hover:text-indigo-500 transition-colors">
37
<Focus size={18} />
38
</div>
39
</div>
40
41
<button
42
onClick={handleActivate}
43
className="mt-6 w-full bg-neutral-900 dark:bg-neutral-800 hover:bg-indigo-600 dark:hover:bg-indigo-800 text-white dark:text-neutral-200 font-medium py-4 transition-all duration-300 transform active:scale-[0.97] flex items-center justify-center"
44
>
45
立即激活输入框
46
</button>
47
</div>
48
</div>
49
50
{/* 教学引导 */}
51
<p className="mt-8 text-center text-neutral-400 text-sm font-light">
52
点击卡片任意位置或按钮,<br />
53
底层使用 <code className="bg-neutral-200 px-1 rounded text-neutral-700">useRef.current.focus()</code> 实现
54
</p>
55
</div>
56
</div>
57
);
58
}

4、总结

初学者最大的误区,就是不假思索的滥用 useState,把所有的变量都定义为状态值. 这种做法会导致组件的重新渲染过于频繁,从而影响性能.

合理的做法是在定义变量时,依据是否会导致组件重新渲染,来决定使用 useState 还是 useRef.

除此之外,我们还可以使用 useRef 来记录定时器的引用,大家可以自行尝试,在后续的课程中,我们还会继续学习 refuseImperativeHandle 的结合使用方法

专栏首页
到顶
专栏目录