一、为何需要 useRef?
在 React 开发中,我们经常需要处理两种核心问题:如何访问 DOM 元素 和 如何存储不随渲染更新的变量 。虽然 useState
和 useEffect
是大多数场景的首选工具,但它们并不适用于所有需求。
比如,你可能遇到这样的场景:
- 用户填写表单后,点击"提交"按钮时自动聚焦到某个输入框。
- 在组件中设置一个计时器,但关闭组件时需要清除计时器。
- 需要记录组件上一次的某个状态值(比如上一次的
props
或state
),以便与当前值进行比较。
这时候,useState
或 useEffect
可能无法直接解决问题,而 useRef
就成了我们的"秘密武器"。接下来,我将通过这篇文章,带你了解useRef的使用。
二、useRef 的概念及核心特性
什么是 useRef?
useRef
是 React 提供的一个 Hook,用于创建一个可变的引用对象 ,这个对象的 .current
属性可以指向任何值(如 DOM 元素、数字、字符串等),并且在组件的整个生命周期内保持不变。
你可以把它想象成一个"抽屉"或"保险箱":
- 你可以随时往里面放东西(比如 DOM 元素、计时器 ID),也可以随时取出。
- 即使组件重新渲染,里面的内容也不会被清空,而是始终保持不变。
核心特性
- 访问 DOM 元素
通过ref
直接操作 HTML 元素,比如获取输入框的值、触发聚焦、滚动到特定位置等。 - 保持值不触发重渲染
修改.current
的值时,不会触发组件重新渲染。这很适合存储不需要驱动 UI 更新的数据。 - 与函数组件协同工作
在函数组件中,useRef
可以像类组件的this
一样保存状态。比如,在类组件中,我们可能会用this.timerId
存储计时器 ID,而在函数组件中,useRef
就是这个角色的完美替代者。
三、useRef 的使用方法
1. 创建 ref 对象并绑定 DOM 元素
javascript
Jsx
深色版本
import React, { useRef } from 'react';
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>聚焦输入框</button>
</div>
);
}
语法与特性解析
const inputRef = useRef(null);
这是useRef
的基本语法:useRef(initialValue)
。它接收一个初始值(这里是null
),并返回一个对象,该对象具有一个.current
属性。这个返回的对象在组件的整个生命周期中是恒定不变的(即每次渲染都指向同一个对象)。<input ref={inputRef} />
这里将useRef
创建的inputRef
对象通过ref
属性绑定到<input>
元素上。React 会在组件挂载时自动将该 DOM 元素的引用赋值给inputRef.current
。当组件卸载时,inputRef.current
会被设为null
。inputRef.current.focus();
在事件处理函数中,我们通过访问inputRef.current
来获取对 DOM 元素的引用,并调用其原生方法focus()
。关键点 :useRef
提供了一种安全、直接的方式访问 DOM,而不会触发组件重新渲染。
特性总结 :
useRef
创建的 ref 对象是一个"稳定"的引用,其.current
属性可以被随时读取和修改,且修改它不会引起组件重新渲染。
2. 保存不随渲染更新的值
ini
Jsx
深色版本
import React, { useRef, useEffect } from 'react';
function Counter() {
const countRef = useRef(0);
const [count, setCount] = React.useState(0);
useEffect(() => {
countRef.current = count;
}, [count]);
const increment = () => {
setCount(prev => prev + 1);
console.log('当前计数(来自 ref):', countRef.current);
};
return (
<div>
<p>当前计数: {count}</p>
<button onClick={increment}>+1</button>
</div>
);
}
语法与特性解析
const countRef = useRef(0);
我们创建了一个 ref 对象countRef
,初始值为0
。与useState
不同,useRef
的初始值只在组件首次渲染时被使用,后续渲染中不会重新创建。countRef.current = count;
在useEffect
中,我们将当前的count
值同步到countRef.current
。这里的关键是:修改countRef.current
不会触发组件重新渲染 。这使得useRef
成为存储"副作用"或"临时状态"的理想选择。console.log(countRef.current);
在increment
函数中,我们读取countRef.current
。注意,此时countRef.current
保存的是上一次渲染时的count
值(因为useEffect
是在渲染后异步执行的)。这展示了useRef
如何帮助我们访问"上一个"状态。
特性总结 :
useRef
可以用来存储任何可变值,并且这个值在组件的整个生命周期中保持不变,修改它不会触发重渲染,非常适合存储不需要驱动 UI 更新的数据。
3. 避免重复计算与副作用清理
javascript
Jsx
深色版本
import React, { useRef, useState } from 'react';
function Timer() {
const intervalRef = useRef(null);
const startTimer = () => {
intervalRef.current = setInterval(() => {
console.log('计时器触发');
}, 1000);
};
const stopTimer = () => {
clearInterval(intervalRef.current);
};
return (
<div>
<button onClick={startTimer}>开始计时</button>
<button onClick={stopTimer}>停止计时</button>
</div>
);
}
语法与特性解析
const intervalRef = useRef(null);
我们创建了一个 ref 对象来存储计时器的 ID。这个 ID 是一个数字,但useRef
可以存储任何类型的值。intervalRef.current = setInterval(...);
当点击"开始计时"按钮时,setInterval
返回一个计时器 ID,我们将其赋值给intervalRef.current
。由于useRef
的稳定性,这个 ID 会被安全地保存下来,即使组件重新渲染也不会丢失。clearInterval(intervalRef.current);
点击"停止计时"时,我们通过intervalRef.current
获取之前保存的计时器 ID,并调用clearInterval
来清除计时器。如果没有useRef
,我们可能无法在多个事件处理函数之间共享这个 ID。
特性总结 :
useRef
是管理副作用(如计时器、订阅、DOM 操作)的理想工具,因为它提供了一个持久的"容器"来存储这些副作用的引用,确保它们可以在组件的不同部分被安全地访问和清理。
结语:
useRef
作为 React 开发中不可或缺的工具,它既解决了 DOM 操作的痛点,又为状态管理提供了灵活的补充。
然而,在使用 useRef
时,务必不要滥用。如果某个值需要驱动 UI 更新,优先选择 useState
;如果只是需要缓存值或操作 DOM,再使用 useRef
。