React 19 对 ref 的传递方式做了一项非常受欢迎的简化:ref 可以直接作为函数组件的普通 prop 传递,不再强制使用 forwardRef 包装。这一改变让代码更直观,也标志着 forwardRef 将逐步退出历史舞台。
旧方式:必须使用 forwardRef
在 React 18 及之前,函数组件默认不能接收 ref 属性。如果子组件需要将 ref 转发给内部的 DOM 元素或类组件实例,必须用 forwardRef 包裹组件,并通过第二个参数接收 ref。
// React 18 写法:必须用 forwardRef
import { forwardRef, useRef } from 'react';
const Input = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});
function Form() {
const inputRef = useRef(null);
const focus = () => inputRef.current?.focus();
return (
<>
<Input ref={inputRef} placeholder="请输入" />
<button onClick={focus}>聚焦</button>
</>
);
}
forwardRef 的存在让简单的 ref 转发多了一层样板代码,而且在 TypeScript 中还需要额外处理泛型定义,增加了学习成本。
新方式:ref 作为普通 prop 直接传递
React 19 解除了这一限制,你可以像传递其他 prop 一样传递 ref,不再需要 forwardRef。组件直接从 props 中解构得到 ref。
// React 19 写法:ref 作为普通 prop,简洁明了
import { useRef } from 'react';
function Input({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />;
}
function Form() {
const inputRef = useRef(null);
const focus = () => inputRef.current?.focus();
return (
<>
<Input placeholder="请输入" ref={inputRef} />
<button onClick={focus}>聚焦</button>
</>
);
}
注意:这里 ref 仍然是 React 内置的特殊 prop,但 React 19 允许它像普通 prop 一样被解构。传递给组件的 ref 会被 React 自动识别为对 DOM 元素或组件实例的引用。
实际使用示例
1. 将 ref 传递给子组件的 DOM 元素
function TextBox({ label, ref }) {
return (
<div>
<label>{label}</label>
<input ref={ref} />
</div>
);
}
function App() {
const inputRef = useRef();
return <TextBox label="用户名" ref={inputRef} />;
}
2. 利用 ref 回调函数
function MeasureBox({ onMount, ref }) {
return (
<div ref={(node) => {
// 可以同时使用 ref 和自定义逻辑
if (ref) ref.current = node;
if (onMount) onMount(node);
}}>
内容
</div>
);
}
React 19 仍支持 ref 回调函数,两者可以并存。
TypeScript 中的类型标注
在 TypeScript 中,直接通过 props 传递 ref 需要合适的类型声明。你需要从 react 中导入 Ref 类型,并用泛型指定引用的元素类型。
import { useRef, type Ref } from 'react';
interface InputProps {
placeholder: string;
ref: Ref<HTMLInputElement>; // ref 定义为 prop
}
function Input({ placeholder, ref }: InputProps) {
return <input placeholder={placeholder} ref={ref} />;
}
// 使用
function App() {
const inputRef = useRef<HTMLInputElement>(null);
return <Input placeholder="姓名" ref={inputRef} />;
}
如果你希望组件同时支持自身的 ref 和传递给子元素的 ref,可以使用 useImperativeHandle 配合新的写法(但 useImperativeHandle 暂时仍需配合 forwardRef;React 19 已计划后续优化,当前版本可直接使用新 prop 方式暴露实例方法,无需 useImperativeHandle)。
forwardRef 逐步废弃:迁移建议
- 新项目:直接使用 ref 作为普通 prop,不再引入
forwardRef。 - 旧项目:可以继续使用
forwardRef,React 19 完全兼容旧写法,不会报错。官方没有给出移除 deadline,会在未来几个大版本中保持兼容。 - 逐步迁移:在重构组件时,去掉
forwardRef包装,将 ref 改为从 props 中解构即可。大部分代码只需要改动组件定义处,使用方无需修改。
// 旧
const OldInput = forwardRef((props, ref) => <input {...props} ref={ref} />);
// 新
function NewInput({ ref, ...rest }) {
return <input ref={ref} {...rest} />;
}
注意事项
- ref 仍具有特殊行为:虽然 ref 可以作为 prop 传递,但其底层仍是 React 的引用机制,不要试图将它当作普通数据传递(例如用于渲染逻辑判断)。
- 不要与普通的
refprop 命名冲突:如果组件内部已经定义了一个名为ref的自定义 prop(非 React 引用),需要重命名以避免冲突。但这种情况极少见,通常约定使用innerRef或类似名称。 - 条件性地使用 ref:和之前一样,ref 只在需要直接访问 DOM 或组件实例时使用,大部分场景应优先使用状态和事件。
这一改进进一步降低了 React 的心智负担,让组件封装更加自然。它也是 React 不断“去模板化”、拥抱原生 JavaScript 理念的体现。