React 中 forwardRef 传递 ref 给子组件

FreeGuideOnline 最新 2026-07-07

什么是 forwardRef

在 React 中,ref 通常用于获取 DOM 元素或类组件实例的引用。然而,默认情况下无法直接将 ref 通过属性传递给函数子组件,因为 React 不会将 ref 作为普通的 prop 向下传递。forwardRef 正是为解决这一问题而生的高阶函数,它允许函数组件接收并转发 ref 给其内部的某个 DOM 元素或子组件。

简单来说,forwardRef 是 React 提供的一种透传 ref 的机制,让父组件能够直接访问子组件内部的节点或实例。


为什么需要 forwardRef

普通函数组件无法接收 ref

如果你直接给一个函数组件传递 ref,React 会在控制台报错,并且 ref.currentnull

function MyInput(props) {
  return <input {...props} />;
}

function Parent() {
  const inputRef = useRef(null);
  return <MyInput ref={inputRef} />; // ⚠️ 警告:函数组件不能接收ref
}

面对这种场景,要么将组件改为 class 组件,要么使用 forwardRef 来显式转发 ref

封装可复用组件时的刚性需求

很多 UI 组件(如按钮、输入框、自定义弹窗)内部包含原生 DOM 节点。为了使使用者能够通过 ref 聚焦、测量尺寸或直接操作 DOM,就必须使用 forwardRef 将内部元素暴露出去。


forwardRef 的基本语法

React.forwardRef 接受一个渲染函数,该函数接收 propsref 两个参数,并返回 React 元素。

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="fancy-button">
    {props.children}
  </button>
));

现在父组件可以将 ref 传递给 FancyButton,并最终附加到内部的 <button> 上。

function App() {
  const buttonRef = useRef(null);

  useEffect(() => {
    console.log(buttonRef.current); // 直接访问button DOM节点
  }, []);

  return <FancyButton ref={buttonRef}>点击</FancyButton>;
}

实际应用场景

1. 聚焦输入框

最常见的场景是在包裹 input 的组件中暴露 ref,让父组件可以随时调用 focus()

const TextInput = React.forwardRef((props, ref) => (
  <input type="text" ref={ref} {...props} />
));

function Form() {
  const inputRef = useRef();
  return (
    <>
      <TextInput ref={inputRef} placeholder="输入内容..." />
      <button onClick={() => inputRef.current.focus()}>聚焦输入框</button>
    </>
  );
}

2. 在 HOC 中传递 ref

当使用高阶组件包装函数组件时,外层组件会“吞噬”掉 ref。此时需要用 forwardRefref 转发到被包裹的组件。

function withLogging(WrappedComponent) {
  return React.forwardRef((props, ref) => {
    console.log('组件渲染:', WrappedComponent.name);
    return <WrappedComponent {...props} ref={ref} />;
  });
}

3. 测量子组件尺寸或位置

ref 转发给需要测量的 DOM 元素,配合 useRefResizeObservergetBoundingClientRect() 实现动态布局。


与 useImperativeHandle 结合使用

有时我们并不希望将整个 DOM 节点暴露给父组件,而是希望提供一个受限的、自定义的命令式接口。这时可以结合 forwardRefuseImperativeHandle 实现。

const CustomInput = React.forwardRef((props, ref) => {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    clear: () => {
      inputRef.current.value = '';
    },
  }));

  return <input ref={inputRef} {...props} />;
});

父组件使用时只能访问 focusclear 方法,无法直接操作 DOM 的其他属性,保证了封装性和安全性。

function Parent() {
  const inputRef = useRef();

  return (
    <>
      <CustomInput ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>聚焦</button>
      <button onClick={() => inputRef.current.clear()}>清空</button>
    </>
  );
}

在 TypeScript 中使用 forwardRef

在 TypeScript 项目中,需要明确指定 ref 的类型以及组件的 props 类型。

interface Props {
  label: string;
}

const MyInput = React.forwardRef<HTMLInputElement, Props>(({ label }, ref) => (
  <div>
    <span>{label}</span>
    <input ref={ref} />
  </div>
));
  • 第一个泛型参数是 ref 所指向的元素类型(HTMLInputElement)。
  • 第二个泛型参数是组件的 props 类型。

常见错误与注意事项

1. 忘记在渲染函数中附加 ref

forwardRef 接收的回调必须显式把 ref 绑定到内部元素,否则 ref 仍然为 null

// ❌ 错误:没有使用ref
const Comp = React.forwardRef((props, ref) => <div>...</div>);

// ✅ 正确
const Comp = React.forwardRef((props, ref) => <div ref={ref}>...</div>);

2. 多处转发 ref

一个组件只能转发一个 ref。如果需要多个内部元素被外部访问,应使用 useImperativeHandle 提供一个组合的对象,或者通过其他方式传递多个 ref(如回调模式)。

3. 函数组件上仍然不要直接传递 ref

即使使用了 forwardRef,也应当只将它用在转发场景中。对于普通的函数组件,依然不建议将其视为 DOM 元素来附加 ref,而应该设计好接口。


总结

  • forwardRef 让函数组件有能力接收并转发 ref,消除了封装组件时的最后一层障碍。
  • useImperativeHandle 配合,可以对外暴露可控的实例方法,保持组件封装性。
  • 在 HOC、UI 库开发和复杂交互场景下,forwardRef 是确保 ref 正确传递的关键工具。

掌握 forwardRef,能让你的组件设计更加灵活、专业,更符合 React 组件化的深层逻辑。