目录 (Outline)
- 一、 Ref 的前世今生:从字符串到 Hook
- 二、 React 19 的重大变革:ref as a Prop
- 三、 废弃 forwardRef:为什么要这么做?
- 四、 现代用法:ref 与 useImperativeHandle 的结合
- 五、 实战:构建一个可控的自定义输入组件
- 六、 总结与最佳实践
一、 Ref 的前世今生:从字符串到 Hook
1. 历史背景
在 React 的早期版本(0.x 到 15.x),我们通过字符串来定义 ref。
// 远古时期的写法
<input ref="myInput" />
// 访问方式
this.refs.myInput.focus();
这种方式存在严重的性能问题和命名冲突风险。
2. 标志性事件
- React 16.3:引入
React.createRef()和React.forwardRef()。 - React 16.8:引入
useRefHook,让函数组件也能方便地处理引用。 - React 19:正式支持将
ref作为组件的普通 Prop 传递,并计划逐步移除forwardRef。
二、 React 19 的重大变革:ref as a Prop
在 React 19 之前,如果你想把 ref 传递给子组件,必须使用 forwardRef 高阶组件。否则,ref 会被 React 拦截,子组件无法在 props 中获取它。
1. 新特性描述
在 React 19 中,ref 不再是特殊关键字。你可以像传递 className 或 onClick 一样传递 ref。
2. 代码示例:React 19 的写法
// 子组件
function MyInput({ label, ref }) {
return (
<label>
{label}
<input ref={ref} />
</label>
);
}
// 父组件使用
function App() {
const inputRef = useRef(null);
return <MyInput label="用户名" ref={inputRef} />;
}
三、 废弃 forwardRef:为什么要这么做?
forwardRef 一直被开发者吐槽「语法冗长」且「难以理解」。
1. 痛点分析
- 嵌套地狱:多层 ref 传递时,代码变得极其混乱。
- 类型断开:在 TypeScript 中使用
forwardRef往往需要复杂的泛型定义,极易出错。
2. 带来的变化
通过将 ref 作为 Prop,组件定义回归到了纯粹的函数形式,不再需要高阶组件的包裹。这极大地提升了代码的可读性和可维护性。
四、 现代用法:ref 与 useImperativeHandle 的结合
虽然我们现在可以直接传递 ref,但有时我们不希望暴露整个 DOM 节点,而是只暴露特定的方法。
1. 核心原理
useImperativeHandle 允许你自定义通过 ref 暴露给父组件的实例值。
2. 代码示例
import { useImperativeHandle, useRef } from 'react';
function CustomModal({ ref }) {
const dialogRef = useRef(null);
useImperativeHandle(ref, () => ({
// 仅暴露这两个方法
open: () => dialogRef.current.showModal(),
close: () => dialogRef.current.close(),
}));
return <dialog ref={dialogRef}>这是一个模态框</dialog>;
}
五、 实战:构建一个可控的自定义输入组件
让我们结合 React 19 的新特性,构建一个具有自动聚焦能力的组件。
import { useRef, useImperativeHandle } from 'react';
const FancyInput = ({ ref, placeholder }) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
shake: () => {
inputRef.current.classList.add('shake');
setTimeout(() => inputRef.current.classList.remove('shake'), 500);
}
}));
return (
<input
ref={inputRef}
placeholder={placeholder}
className="fancy-input"
/>
);
};
// 父组件调用
export default function Form() {
const fancyInputRef = useRef(null);
return (
<>
<FancyInput ref={fancyInputRef} placeholder="请输入内容" />
<button onClick={() => fancyInputRef.current.focus()}>聚焦</button>
<button onClick={() => fancyInputRef.current.shake()}>抖动提示</button>
</>
);
}
六、 总结与最佳实践
- 拥抱简洁:新项目建议直接使用 Prop 传递 ref,不再使用
forwardRef。 - 权限最小化:尽量配合
useImperativeHandle仅暴露必要的方法,保护子组件内部状态。 - 类型安全:在 TS 中,直接为
refProp 指定类型即可,无需再为 HOC 烦恼。
React 19 对 ref 的改造,是其追求「纯粹函数式组件」愿景的重要一步。