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的类型定义来看:
ForwardedRef<T>的类型为((instance: T | null) => void) | MutableRefObject<T | null> | null,要求ref对象的current是T | null类型。useRef<T>()(无初始值)返回MutableRefObject<T | undefined>,而useRef<T>(null)返回MutableRefObject<T | null>,undefined与null类型不兼容导致报错。- 子组件手动在
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
相关产品推荐
相关产品推荐

