人人都会AI编程

26.4 ref 作为函数参数、forwardRef 逐步废弃

更新时间:2026-07-11

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 的引用机制,不要试图将它当作普通数据传递(例如用于渲染逻辑判断)。
  • 不要与普通的 ref prop 命名冲突:如果组件内部已经定义了一个名为 ref 的自定义 prop(非 React 引用),需要重命名以避免冲突。但这种情况极少见,通常约定使用 innerRef 或类似名称。
  • 条件性地使用 ref:和之前一样,ref 只在需要直接访问 DOM 或组件实例时使用,大部分场景应优先使用状态和事件。

这一改进进一步降低了 React 的心智负担,让组件封装更加自然。它也是 React 不断“去模板化”、拥抱原生 JavaScript 理念的体现。