React19的ref可以直接当prop了,forwardRef还写吗?

作者:晚风写码时间:2026-11-13 09:41:05浏览:398

深入解析 React 19 中 ref 作为普通 prop 直接传递的新特性,对比 React 18 的 forwardRef 写法差异,提供平滑迁移步骤、TypeScript 类型处理及场景决策指南。

React19的ref可以直接当prop了,forwardRef还写吗?
React 19 升级指南 适用阶段:React 18 到 19 迁移期 阅读时间:约 7 分钟

核心结论先行:在 React 19 中,函数组件已经可以直接将 ref 当作普通 prop 接收,不再需要使用 forwardRef 包裹。React 官方已将 forwardRef 正式标记为弃用(Deprecated),未来主版本将彻底移除。对于全新项目与自用业务组件,坚决不再写 forwardRef;如果是需要同时支持 React 18/17 的开源通用组件库,则现阶段仍需保留 forwardRef 以保证向下兼容。

一、底层机制:为什么以前需要 forwardRef,现在不需要了?

在 React 18 及更早的版本中,JSX 在被编译器(Babel 或 SWC)转换为虚拟 DOM 节点时,key 和 ref 被视为特殊保留关键字。React 的底层运行时会在将属性注入组件函数之前,主动把 ref 从 props 对象中剔除。如果在普通的函数组件上传递了 ref,组件内部在 props.ref 中只能拿到 undefined,同时开发控制台还会抛出一条警告:“Function components cannot be given refs”。

为了解决函数组件向下透传 DOM 引用的问题,React 16.3 引入了 forwardRef。它本质上是一个高阶包装器,要求组件改为接收两个参数 (props, ref),并由 React 运行时把剥离出来的 ref 注入到第二个参数中。这种设计不仅打破了“组件参数只有 props 一个对象”的统一心智模型,还引入了额外的嵌套层级、复杂的泛型参数以及 DevTools 中匿名组件命名(displayName)的问题。

在 React 19 中,React 核心团队彻底重构了 JSX 运行时与属性分发逻辑。ref 不再被特殊剥离,而是被当成与 className、onClick 完全等同的一等公民属性。组件函数回归为纯粹的单一入参形态,直接在解构中读取 ref 即可。

React19的ref可以直接当prop了,forwardRef还写吗?
图 1:React 18 与 React 19 底层属性传递与组件解构机制对比

二、写法对照:从 forwardRef 到普通 ref prop

理解机制转变之后,我们来看最常见的输入框(Input)组件在新旧版本中的具体代码形态差异。

1. React 18 旧写法(二参数 + 高阶包裹)

// React 18:必须用 forwardRef 包裹,且显式补充 displayName
import React, { forwardRef } from 'react';

interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  label: string;
}

export const CustomInput = forwardRef<HTMLInputElement, InputProps>(
  ({ label, ...restProps }, ref) => {
    return (
      <div className="input-group">
        <label>{label}</label>
        <input ref={ref} {...restProps} />
      </div>
    );
  }
);

CustomInput.displayName = 'CustomInput';

2. React 19 新写法(标准单参数解构)

// React 19:普通的函数组件,ref 直接出现在 props 中
import React from 'react';

interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  label: string;
  ref?: React.Ref<HTMLInputElement>; // 在 React 19 中直接声明在 props 中
}

export function CustomInput({ label, ref, ...restProps }: InputProps) {
  return (
    <div className="input-group">
      <label>{label}</label>
      <input ref={ref} {...restProps} />
    </div>
  );
}

对比两段代码可以发现,React 19 的书写体验有了质的提升:

  • 结构扁平清晰:移除了高阶包裹函数,组件恢复成最基础的 function 声明,不需要额外的括号和嵌套。
  • 调试名称天然保留:无需为了 React DevTools 正确显示而单独挂载 displayName。
  • 参数统一规范:所有输入属性全部归拢于第一个参数对象,支持属性重命名与默认值设定。

三、已有项目平滑迁移的 4 个步骤

当现有项目升级到 React 19 时,不必一次性重写所有组件,但对于正在维护或新建的模块,可以按照以下 4 个步骤有序迁移:

React19的ref可以直接当prop了,forwardRef还写吗?
图 2:将已有 forwardRef 组件重构成 React 19 标准写法的 4 步演进路径
  1. 移除外层包裹:删除 React.forwardRef(...) 包装函数,恢复为普通的具名函数导出 export function Component(props)。
  2. 调整参数列表:删除原本独立的第二参数 ref,在首个入参对象的解构中显式添加 ref(例如 { ref, children, ...rest })。
  3. 更新 TypeScript 类型:删除 forwardRef<RefType, PropsType> 的双泛型配置,直接在 Props 接口内声明 ref?: React.Ref<TargetElement>,或者直接使用 React.ComponentPropsWithRef<'input'>。
  4. 校验命令式 Handle:如果组件配合了 useImperativeHandle,检查其首个入参,直接传入解构出来的 ref prop 即可。

四、配合 useImperativeHandle 的新姿势

很多开发者在初见“弃用 forwardRef”时会产生疑虑:如果组件需要向父级暴露命令式方法(例如 focus()、scrollIntoView() 或自定义校验方法),原本依赖 forwardRef 的 useImperativeHandle 还能继续使用吗?

答案是:完全可以,而且写法更为直接。useImperativeHandle 的机制并没有改变,它只需要接收一个有效的 ref 实例作为第一个参数。在 React 19 中,这个 ref 同样直接取自 props:

import React, { useRef, useImperativeHandle } from 'react';

export interface ModalHandle {
  open: () => void;
  close: () => void;
}

interface ModalProps {
  title: string;
  ref?: React.Ref<ModalHandle>;
}

export function ModalDialog({ title, ref }: ModalProps) {
  const dialogRef = useRef<HTMLDialogElement>(null);

  // 直接将来自 props 的 ref 传给 useImperativeHandle
  useImperativeHandle(ref, () => ({
    open: () => dialogRef.current?.showModal(),
    close: () => dialogRef.current?.close(),
  }));

  return (
    <dialog ref={dialogRef} className="app-modal">
      <h3>{title}</h3>
      <button onClick={() => dialogRef.current?.close()}>关闭</button>
    </dialog>
  );
}

可以看到,命令式句柄的暴露不再强制捆绑 forwardRef,代码逻辑紧凑自洽。

五、TypeScript 类型生态的改变

在 React 18 中,为 forwardRef 编写正确的 TypeScript 类型常常是团队新人的痛点。由于 forwardRef<T, P> 的泛型顺序是“Ref 类型在前,Props 类型在后”,不仅反直觉,而且在需要同时支持泛型组件(Generic Components)时,往往需要复杂的类型断言或额外写辅助函数。

在 React 19(配合最新版本的 @types/react@^19.0.0)中,这一体验得到了极大改善:

对比维度 React 18 旧写法 React 19 新特性
组件定义方式 const C = forwardRef<Ref, Props>((props, ref) => ...) function C(props: Props)
泛型组件支持 非常复杂,必须借助 as unknown as 类型强转 天然支持:function Select<T>(props: SelectProps<T>)
内置 Props 工具类型 需区分 ComponentPropsWithoutRef 与 WithRef ComponentProps 默认包含 ref,模型彻底统一
Ref 属性解构与重命名 不支持重命名(因为 ref 是独立参数) 完全支持:({ ref: targetRef, ...rest })

六、决策指南:什么情况下还必须保留 forwardRef?

虽然 React 19 本身完全支持弃用 forwardRef,但在真实的工程实践中,是否立即全部删除取决于你所维护项目的类型与受众范围:

React19的ref可以直接当prop了,forwardRef还写吗?
图 3:“还写不写 forwardRef?”项目类型与场景决策流程树

场景 1:全新业务项目或微前端应用(强推荐:坚决不写)

如果你的应用基线依赖已经明确锁定在 React 19("react": "^19.0.0"),请在新写的每一个组件中直接使用 ref prop。不必保留任何 forwardRef,这是 React 团队明确给出的未来演进路线。

场景 2:从 React 18 升级的现有业务系统(策略:暂不急改,按需重构)

React 19 保证了严格的向后兼容性。现存代码中的 forwardRef 在运行时仍然能正常执行,不会中断业务或造成构建错误。React 仅会在开发模式的控制台中打印弃用提示。建议制定渐进重构策略:在重构旧模块或修复缺陷时顺手改写,或者使用官方提供的 codemod 批量自动化转换。

场景 3:面向社区发布的通用 UI 组件库 / SDK(必须保留:向后兼容)

如果你的组件库发布的 npm 包需要被 React 18 甚至 React 17 的宿主项目安装使用(例如 peerDependencies: ">=18.0.0"),则现阶段必须继续保留 forwardRef。因为 React 18 的运行时无法识别作为普通 prop 传递的 ref,一旦宿主在 React 18 下直接传递 ref,组件将无法接收到底层 DOM 引用,引发功能异常。

⚠️ 组件库跨版本发布注意事项

若组件库计划完全弃用 forwardRef,请务必作为破坏性变更(Breaking Change)发布新的主版本(Major Version),并在 peerDependencies 中明确将 react 的最低要求提升至 >=19.0.0。

七、常见疑问与避坑事项

Q1:如果不修改旧组件中的 forwardRef,React 19 会报错崩溃吗?

不会。React.forwardRef 目前只是被标记为 deprecated,其功能在 React 19 的整个生命周期内依然保持稳定。React 团队通常会留出至少一个完整的大版本缓冲周期,因此存量项目可以平稳过渡。

Q2:ref 作为普通 prop 后,是否可以传递给子组件再次深层透传?

完全可以。在 React 18 中,如果想将 ref 从 A 穿透给 B 再传给 input,往往需要层层包裹 forwardRef;而在 React 19 中,由于 ref 就是普通的 JavaScript 变量,你可以像传递任何回调函数一样自由传递或重命名:

// 自由转发或解构重命名
function OuterWrapper({ ref, ...props }) {
  return <InnerSection targetRef={ref} {...props} />;
}

Q3:类组件(Class Components)支持将 ref 当作普通 prop 吗?

不支持。类组件的 ref 属性在语法上始终绑定为该类组件的实例对象(Component Instance)。因此,直接将 ref 作为 prop 解构的特性仅适用于函数组件(Function Components)。

八、全文总结与升级检查清单

React 19 将 ref 归为普通 prop 是一项极具工程价值的瘦身改动,消除了长期以来函数组件为了处理 ref 而不得不引入的复杂封装。以下是升级决策的核心备忘卡片:

React19的ref可以直接当prop了,forwardRef还写吗?
图 4:React 19 Ref 全景核心知识与升级落地要点总结
📋 落地实操速查表
  • 新写组件:一律使用 function MyComp({ ref, ...props }),不再导入 forwardRef。
  • TypeScript:在 Props 接口中添加 ref?: React.Ref<T>,摆脱双泛型包裹。
  • 命令式方法:直接调用 useImperativeHandle(ref, () => ({ ... }))。
  • 组件库维护:需兼容 React 18 则保留 forwardRef;纯 React 19 库果断废弃。
作者最新文章
相关文章
更多