前言
在 React 的声明式编程中,我们习惯于通过“状态驱动视图”。但在某些场景下,我们需要直接操作 DOM,或者存储一些不希望触发页面重新渲染的数据。这时,useRef 就成了不可或缺的利器。
一、 核心概念:什么是 useRef?
useRef 会返回一个可变的 ref 对象,其内部只有一个 current 属性。
- 持久性:这个 ref 对象在组件的整个生命周期内保持不变,即使组件多次重新渲染,
ref.current也会指向同一个引用。 - 静默性:修改
current的值不会触发组件的重新渲染。这与useState有本质区别。
语法参考
const myRef = useRef<T>(initialValue);
- initialValue:
current属性的初始值。该值仅在首次渲染时生效,后续渲染会被忽略。
二、 三大经典使用场景
1. 获取 DOM 元素并调用原生 API
这是最常用的场景。通过将 ref 属性绑定到 JSX 标签上,可以直接访问真实的 DOM。
import React, { useRef, useEffect } from 'react';
const AutoFocusInput: React.FC = () => {
// 定义 HTMLInputElement 类型的 ref
const inputRef = useRef<HTMLInputElement>(null);
const handleFocus = () => {
// 通过 current 访问原生 DOM API
inputRef.current?.focus();
if (inputRef.current) {
inputRef.current.style.border = "2px solid blue";
}
};
return (
<div style={{ padding: '20px' }}>
<input ref={inputRef} type="text" placeholder="点击按钮聚焦" />
<button onClick={handleFocus}>手动聚焦</button>
</div>
);
};
export default AutoFocusInput;
2. 保存定时器或外部监听器的引用
如果将定时器 ID 存入 useState,每次修改都会导致组件重绘。使用 useRef 可以在不影响渲染的情况下保存这些引用,并在卸载时安全清除。
import React, { useRef, useEffect } from 'react';
const Timer: React.FC = () => {
const timerIdRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
// 保存定时器 ID
timerIdRef.current = setInterval(() => {
console.log('数据上报中...');
}, 1000);
// 组件卸载时,利用 ref 准确清除
return () => {
if (timerIdRef.current) {
clearInterval(timerIdRef.current);
}
};
}, []);
return <div>后台监控运行中,请查看控制台...</div>;
};
export default Timer;
3. 记录“上一次”的状态值(对比前后变化)
由于 useRef 不随渲染更新,我们可以利用它捕捉渲染快照,用于对比逻辑。
import React, { useState, useEffect, useRef } from 'react';
const PrevStateDemo: React.FC = () => {
const [count, setCount] = useState<number>(0);
const prevCountRef = useRef<number>(0);
useEffect(() => {
// 渲染完成后,将当前的 count 存入 ref,供下一次渲染对比
prevCountRef.current = count;
}, [count]);
return (
<div>
<h1>当前值: {count}</h1>
<h2>上一次的值: {prevCountRef.current}</h2>
<button onClick={() => setCount(c => c + 1)}>增加</button>
</div>
);
};
export default PrevStateDemo;