在 React 中,useRef、ref 属性以及 forwardRef 是处理"引用"(访问 DOM 节点或组件实例)的核心概念。它们经常一起使用,但职责完全不同。
以下是它们的核心区别、使用方法及组合示例:
1. 核心概念与区别
| 特性 | ref (属性) |
useRef (Hook) |
forwardRef (API) |
|---|---|---|---|
| 本质 | JSX 中的一个特殊属性。 | 一个 React Hook,用于创建 ref 对象。 | 一个高阶函数,用于将 ref 转发给子组件。 |
| 主要作用 | 标记"我想访问这个元素/组件"。 | 1. 创建可变的容器 (ref.current)。 2. 存储不触发重渲染的数据。 3. 获取 DOM 节点。 |
允许父组件 通过 ref 直接访问函数子组件内部的 DOM 节点。 |
| 触发重渲染 | 否 | 否 (修改 current 不会重渲染) |
否 |
| 典型场景 | 挂在到 <input> 或自定义组件上。 |
在组件内部初始化引用,存储定时器、DOM 节点等。 | 编写可复用的自定义组件(如 Input, Button),并希望外部能控制其焦点或动画。 |
2. 详细用法解析
A. useRef:创建引用的容器
useRef 返回一个对象 { current: ... },这个对象在组件的整个生命周期内保持不变。
两大用途:
-
访问 DOM 节点 :配合
ref属性使用。 -
存储可变变量 :类似
state但不触发重渲染(适合存定时器、上一次的值等)。import { useRef, useEffect } from 'react';
function MyComponent() {
// 1. 创建 ref 对象,初始值为 null
const inputRef = useRef(null);
const countRef = useRef(0); // 2. 用于存储不触发渲染的变量useEffect(() => {
// 组件挂载后,自动聚焦输入框
// inputRef.current 此时指向真实的 DOM 节点
if (inputRef.current) {
inputRef.current.focus();
}// 修改 countRef.current 不会导致组件重新渲染 countRef.current += 1;}, []);
return (
// 将 ref 对象绑定到 DOM 元素
);
}
B. forwardRef:打通父子组件的引用通道
默认情况下,函数组件不能接收 ref 。如果你在函数组件上使用 ref,它会丢失(或者在旧版本报错)。 forwardRef 的作用就是让函数组件能够"透传"这个 ref,使其指向组件内部的某个 DOM 元素。
使用场景: 当你封装了一个通用组件(如 FancyInput),希望使用它的父组件能直接控制它(如调用 .focus())。
import { forwardRef } from 'react';
// 定义子组件,使用 forwardRef 包裹
// 注意:第二个参数 ref 是由父组件传下来的
const FancyInput = forwardRef((props, ref) => {
return (
<div className="fancy-wrapper">
<label>{props.label}</label>
{/* 将接收到的 ref 绑定到内部的 input 上 */}
<input ref={ref} {...props} />
</div>
);
});
export default FancyInput;
C. 组合使用:父组件通过 useRef + forwardRef 控制子组件
这是最完整的链路:父组件创建 ref (useRef) -> 传给子组件 (ref 属性) -> 子组件接收并转发 (forwardRef) -> 绑定到 DOM。
import { useRef } from 'react';
import FancyInput from './FancyInput'; // 上面定义的组件
function ParentComponent() {
// 1. 父组件创建 ref
const inputElementRef = useRef(null);
const handleFocusClick = () => {
// 3. 父组件直接操作子组件内部的 DOM
if (inputElementRef.current) {
inputElementRef.current.focus();
inputElementRef.current.value = "被父组件控制了!";
}
};
return (
<div>
{/* 2. 将 ref 传给自定义子组件 */}
<FancyInput
ref={inputElementRef}
label="请输入:"
placeholder="点击按钮聚焦我"
/>
<button onClick={handleFocusClick}>
聚焦子组件输入框
</button>
</div>
);
}
3. 常见误区与总结
-
为什么不能直接在函数组件上用
ref?- 函数组件没有实例(不像 Class 组件有
this),所以默认没法把ref绑定到组件本身。forwardRef是一种显式的机制,告诉 React:"我知道我在做什么,请把这个 ref 传递给我内部的某个 DOM"。
- 函数组件没有实例(不像 Class 组件有
-
useRef和useState的区别?useState:数据改变 -> 触发重渲染。useRef:数据 (current) 改变 -> 不触发重渲染。- 技巧 :如果你需要记录一个值但不希望界面刷新(例如记录上一次的 props 值,或者存储
setInterval的 ID),请用useRef。
-
什么时候必须用
forwardRef?- 当你开发库组件 或通用 UI 组件 (如 Ant Design, Material UI 中的输入框、按钮),且需要支持用户通过
ref来控制焦点、动画或测量尺寸时。 - 如果是简单的内部逻辑,通常不需要。
- 当你开发库组件 或通用 UI 组件 (如 Ant Design, Material UI 中的输入框、按钮),且需要支持用户通过
一句话总结
useRef是用来制造引用对象的(在组件内部)。ref是用来挂载引用到元素上的(在 JSX 中)。forwardRef是用来传递引用穿过组件边界的(在子组件定义处)。