React 中,useRef 和forwardRef是什么
在 React 中,useRef 和 forwardRef 都与 Ref(引用) 有关,但它们的作用和使用场景完全不同。
简单来说:
useRef是用来 “创建” 引用的(或者存储跨渲染的数据)。forwardRef是用来 “传递” 引用的(让父组件可以操作子组件内部的 DOM)。
下面详细解释这两个概念。
1. useRef
useRef 是一个 React Hook。
作用
它返回一个可变的 ref 对象,该对象只有一个 current 属性。const myRef = useRef(initialValue);
核心特性
- 访问 DOM 节点:这是最常用的功能。你可以直接获取 HTML 元素的底层 DOM 节点(例如让输入框聚焦、滚动到特定位置)。
- 存储变量(不触发重渲染):它就像一个“盒子”,你可以往
current里存任何数据。修改ref.current不会触发组件重新渲染。这与useState不同(useState改变会触发重渲染)。 - 生命周期保持:在组件的整个生命周期内,返回的同一个 ref 对象保持不变。
代码示例:访问 DOM
jsx
import React, { useRef } from 'react';
function TextInput() {
// 1. 创建 ref
const inputEl = useRef(null);
const onButtonClick = () => {
// 3. 通过 .current 访问原生 DOM API
inputEl.current.focus();
};
return (
<>
{/* 2. 绑定 ref 到元素 */}
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>聚焦输入框</button>
</>
);
}
2. forwardRef
forwardRef 是一个高阶组件(HOC)或 API 函数。
作用
它允许父组件将 ref “转发” (forward) 给子组件内部的某个 DOM 元素。
为什么需要它?
默认情况下,函数组件不能接收 ref 属性。如果你尝试在一个自定义组件上使用 ref(例如 <MyComponent ref={ref} />),React 会报错或忽略它,因为函数组件没有实例。
如果你希望父组件能够获取子组件内部某个 HTML 标签的引用,就需要用 forwardRef 包裹子组件。
代码示例:父组件控制子组件的 Input
jsx
import React, { useRef, forwardRef } from 'react';
// 子组件:使用 forwardRef 包裹
// props 是父组件传的参数,ref 是父组件传下来的引用
const MyInput = forwardRef((props, ref) => {
return (
<input
ref={ref} // 将父组件的 ref 绑定到内部的 input 上
className="fancy-input"
{...props}
/>
);
});
// 父组件
function Parent() {
const inputRef = useRef(null); // 父组件创建 ref
const handleClick = () => {
// 父组件可以直接操作子组件内部的 input DOM
inputRef.current.focus();
inputRef.current.value = "Hello World";
};
return (
<div>
{/* 将 ref 传递给自定义组件 */}
<MyInput ref={inputRef} placeholder="我是子组件" />
<button onClick={handleClick}>父组件控制子组件聚焦</button>
</div>
);
}
3. 它们如何配合使用?
通常的模式是:
- 父组件 使用
useRef创建一个 ref 对象。 - 父组件 将这个 ref 传递给 子组件。
- 子组件 使用
forwardRef接收这个 ref,并将其绑定到自己的某个 HTML 元素上。
进阶:搭配 useImperativeHandle
有时候,你不想把子组件原本的 DOM 节点完全暴露给父组件(为了安全或封装性),只想暴露几个特定的方法(比如 focus, reset)。这时会用到 useImperativeHandle 配合 forwardRef。
jsx
const MyInput = forwardRef((props, ref) => {
const inputRef = useRef();
// 定义暴露给父组件的方法
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
alertValue: () => {
alert(inputRef.current.value);
}
}));
return <input ref={inputRef} />;
});
总结对比
| 特性 | useRef | forwardRef |
|---|---|---|
| 类型 | Hook (钩子) | API / 高阶函数 |
| 主要目的 | 创建引用容器,或存储跨渲染数据 | 传递引用,穿透组件层级 |
| 返回值 | { current: ... } 对象 |
一个 React 组件 |
| 触发渲染 | 修改时不触发渲染 | 本身不涉及渲染逻辑 |
| 使用位置 | 组件内部逻辑中 | 定义组件时包裹组件函数 |
| 一句话概括 | "我要存个东西/我要抓个DOM" | "我要把父组件给我的Ref转交给我的子元素" |