一、forwardRef 是什么?—— 一句话搞懂核心概念
在 React 中,ref 就像一个 “指针”,用于获取 DOM 元素或组件实例的引用。但遇到函数组件时,直接使用 ref 会 “失灵”,因为函数组件没有实例可引用。这时,forwardRef 就派上用场啦!它是 React 提供的一个高阶函数,专门用来将父组件的 ref 转发到子组件的 DOM 元素或类组件实例上,相当于给 ref 搭建了一条 “专属通道”~
1.1 核心语法:给组件装上 “转发器”
const 转发后的组件 = forwardRef((props, ref) => {
// 子组件逻辑,将 ref 绑定到具体的 DOM 或类组件
return <子组件 {...props} ref={ref} />;
});
- 第一个参数是子组件接收的
props,和普通函数组件一致 - 第二个参数
ref是父组件传递的引用,需要显式绑定到子组件内部的 DOM 元素上
二、为什么需要 forwardRef?—— 解决函数组件的 ref 困境
先看一个 “踩坑” 案例:直接给函数组件传 ref 会报错!
// 子组件:普通函数组件
function InputComponent(props) {
return <input type="text" />;
}
// 父组件:尝试用 ref 聚焦输入框
function App() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus(); // 报错!函数组件无法获取 ref
}, []);
return <InputComponent ref={inputRef} />; // 警告:Function components cannot be given refs
}
报错原因:React 规定,ref 只能直接绑定到 DOM 元素或类组件实例上,而函数组件没有实例,所以直接传 ref 会被忽略。
解决方案:用 forwardRef 包装子组件,让它具备转发 ref 的能力,就像给 ref 开了一条 “绿色通道”~
三、forwardRef 实战:从基础到进阶场景
3.1 基础用法:父组件获取子组件的 DOM 元素
结合你提供的代码,我们来实现一个 “父组件控制子组件输入框自动聚焦” 的功能:
import { useRef, useEffect, forwardRef } from 'react';
import './App.css';
// 子组件:接收 props 和 ref,将 ref 绑定到 input 上
function Guang(props, ref) {
// 这里的 ref 是父组件传来的,直接绑定给 input 元素
return (
<div>
<input type="text" ref={ref} /> {/* ref 精准定位到 input */}
</div>
);
}
// 关键:用 forwardRef 包装组件,激活 ref 转发能力
const WrapperGuang = forwardRef(Guang);
function App() {
const inputRef = useRef(null); // 创建 ref 引用
// 组件挂载后自动聚焦输入框
useEffect(() => {
inputRef.current?.focus(); // 通过 ref 操作 DOM,和直接操作 input 一样方便
}, []);
return (
<div className="App">
{/* 给转发后的组件传 ref,就能拿到子组件内部的 input 了 */}
<WrapperGuang ref={inputRef} />
</div>
);
}
export default App;
运行效果:页面加载后,输入框会自动获得焦点,是不是很神奇?😉
核心逻辑:WrapperGuang 通过 forwardRef 获得了转发能力,父组件的 inputRef 跳过了组件层级,直接绑定到了子组件的 input 元素上。
3.2 高阶组件(HOC)中的 ref 转发
高阶组件(HOC)是包装组件的函数,但默认会 “拦截”ref,导致父组件拿不到内部组件的引用。比如下面这个 “日志高阶组件”:
// 高阶组件:打印 props 变化
function logProps(WrappedComponent) {
// 问题:这里的 ref 会指向 LogProps 而不是 WrappedComponent
return function LogProps(props) {
console.log('当前 props:', props);
return <WrappedComponent {...props} />;
};
}
// 使用时,ref 无法指向内部的 Button
const LoggedButton = logProps(Button);
<LoggedButton ref={buttonRef} /> // buttonRef.current 是 LogProps 组件,不是 Button
解决办法:在 HOC 中用 forwardRef 转发 ref:
function logProps(WrappedComponent) {
// 用 forwardRef 接收父组件的 ref
return forwardRef((props, ref) => {
console.log('当前 props:', props);
// 将 ref 转发给被包装的组件
return <WrappedComponent {...props} ref={ref} />;
});
}
// 现在 ref 能正确指向内部的 Button 了
const LoggedButton = logProps(Button);
<LoggedButton ref={buttonRef} /> // buttonRef.current 是 Button 的 DOM 或实例
3.3 嵌套组件:跨层级传递 ref 更优雅
如果组件嵌套多层(父 → 中 → 孙),想在父组件直接获取孙组件的 DOM,无需通过 props 层层传递 ref,用 forwardRef 就能轻松实现:
// 孙组件:绑定 ref 到 DOM
const GrandChild = forwardRef((props, ref) => {
return <div ref={ref}>我是孙组件的 DOM</div>;
});
// 中间组件:直接转发 ref,无需处理 props
const Middle = forwardRef((props, ref) => {
return <GrandChild ref={ref} />; // 把父组件的 ref 传给孙组件
});
// 父组件:直接操作孙组件的 DOM
function Parent() {
const grandChildRef = useRef(null);
useEffect(() => {
console.log('孙组件的 DOM:', grandChildRef.current); // 成功获取!
}, []);
return <Middle ref={grandChildRef} />;
}
优势:中间组件无需关心 ref 的存在,像 “透明管道” 一样让 ref 直接到达目标,代码更简洁~
四、使用 forwardRef 的 3 个注意事项
4.1 ref 必须绑定到 “真实存在” 的目标上
ref 最终必须绑定到 DOM 元素(如 <div>、<input>)或类组件实例上,否则会无效:
// 错误:ref 没有绑定到具体 DOM
const BadComponent = forwardRef((props, ref) => {
return <div>我没有绑定 ref</div>; // ref 没地方去,会是 null
});
// 正确:ref 绑定到 DOM 元素
const GoodComponent = forwardRef((props, ref) => {
return <div ref={ref}>我绑定了 ref</div>; // 正确!
});
4.2 函数组件命名:方便调试
如果用匿名函数定义转发组件,React DevTools 会显示为 ForwardRef,不利于调试。建议用具名函数:
// 不好:匿名函数,调试时看不清组件名
const Input = forwardRef((props, ref) => <input ref={ref} />);
// 好:具名函数,调试时显示为 ForwardRef(InputComponent)
const InputComponent = forwardRef(function InputComponent(props, ref) {
return <input ref={ref} />;
});
4.3 别和 props 里的 “ref” 重名
子组件的 props 中不要用 ref 作为属性名,否则会和 forwardRef 传递的 ref 冲突:
// 错误:props 里的 ref 和 forwardRef 的 ref 重名
const BadComponent = forwardRef((props, ref) => {
// 这里的 props.ref 和 ref 会混淆!
return <div ref={ref}>{props.ref}</div>;
});
// 正确:用其他名字(如 inputRef)
const GoodComponent = forwardRef((props, ref) => {
return <div ref={ref}>{props.inputRef}</div>;
});
五、总结:forwardRef 什么时候用?
简单说:当你需要让父组件的 ref 跳过中间组件,直接到达子组件内部的 DOM 或类组件时,就用 forwardRef!
它的核心价值是:打破函数组件对 ref 的限制,让 ref 像 “靶向导弹” 一样精准到达目标,同时避免用 props 传递 ref 带来的代码冗余。
下次再遇到 “函数组件拿不到 ref” 的问题,记得用 forwardRef 这个神器哦~