You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React 18.X中forwardRef与useRef的类型定义是否兼容?

解决forwardRef与useRef的TypeScript类型不匹配问题

问题场景

在给包含forwardRef和useRef的React组件添加TypeScript类型注解时,子组件注解无错误,但父组件传递ref时出现TS2322类型不匹配错误。

基础JSX示例:

const Child = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    log() {
      console.log("child function");
    }
  }));

  return <h1>Child</h1>;
});

const Parent = () => {
  const ref = useRef();

  return (
    <div>
      <Child ref={ref} />
      <button onClick={() => ref.current.log()}>Click</button>
    </div>
  );
};

子组件原注解代码:

const Child : ForwardRefExoticComponent<Child.Props> = forwardRef<Child.PublicMethods, Child.Props>(
  (
    props: ForwardedRef.Props, // 此处类型错误,ForwardedRef不存在Props类型
    reference: ForwardedRef<Child.PublicMethods>
  ): ReactElement => {
    // ...
  }
);

namespace Child {
  export type Props = Readonly<{ 
    ref?: ForwardedRef<Child.PublicMethods> // 错误:无需手动定义ref属性
  }>
 
  export type PublicMethods = Readonly<{
    log: () => void;
  }>;
}

父组件原代码及错误:

const Parent = () => {
  // 重复声明ref且类型不匹配
  const ref = useRef();
  const ref: React.MutableRefObject<Child.PublicMethods | undefined> =
      useRef<Child.PublicMethods>();

  return (
    <div>
      <Child ref={ref} />
      <button onClick={() => ref.current.log()}>Click</button>
    </div>
  );
};

错误信息:

Type MutableRefObject<Readonly<{ log: () => void; }> | undefined> is not assignable to type MutableRefObject<Readonly<{ log: () => void; }> | null>

错误原因分析

结合React的类型定义来看:

  1. ForwardedRef<T>的类型为((instance: T | null) => void) | MutableRefObject<T | null> | null,要求ref对象的current是T | null类型。
  2. useRef<T>()(无初始值)返回MutableRefObject<T | undefined>,而useRef<T>(null)返回MutableRefObject<T | null>,undefined与null类型不兼容导致报错。
  3. 子组件手动在Props中定义ref属性是多余的——forwardRef会自动为组件添加RefAttributes<T>类型的ref属性。

修正方案

子组件正确写法

去掉手动定义的ref属性,让forwardRef自动处理ref类型,同时修正泛型和参数类型:

import { forwardRef, useImperativeHandle, ReactElement } from 'react';

namespace Child {
  // 子组件props(无额外props则定义为空对象)
  export type Props = Readonly<{}>;
  // 暴露给父组件的方法类型
  export type PublicMethods = Readonly<{
    log: () => void;
  }>;
}

// 泛型第一个参数为暴露的实例类型,第二个为props类型
const Child = forwardRef<Child.PublicMethods, Child.Props>(
  (props: Child.Props, ref) => {
    useImperativeHandle(ref, () => ({
      log() {
        console.log("child function");
      }
    }));

    return <h1>Child</h1>;
  }
);

export default Child;

父组件正确写法

使用useRef时指定初始值为null,让ref的current类型为Child.PublicMethods | null,匹配ForwardedRef的要求:

import { useRef } from 'react';
import Child from './Child';

const Parent = () => {
  // 指定泛型为Child.PublicMethods,初始值设为null
  const ref = useRef<Child.PublicMethods | null>(null);

  return (
    <div>
      <Child ref={ref} />
      {/* 调用前判断current是否存在,避免空值错误 */}
      <button onClick={() => ref.current?.log()}>Click</button>
    </div>
  );
};

export default Parent;

关键总结

  • forwardRef<T, P>的第一个泛型参数T是子组件通过useImperativeHandle暴露给父组件的实例类型。
  • 父组件使用useRef时,必须让ref的current类型为T | null,因此初始值要设为null。
  • 子组件的Props不需要手动声明ref属性,forwardRef会自动为组件添加合法的ref类型。

内容的提问来源于stack exchange,提问作者Takeshi Tokugawa YD

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 12:17:44