React TypeScript封装函数返回组件时如何保留泛型类型
问题背景
- 现有封装的Suspense包裹工具函数实现如下:
import { ComponentType, Suspense } from 'react'; function useSuspense<P extends Record<string, any>>(WrappedComponent: ComponentType<P>) { function Component(props: P) { return ( <Suspense fallback={fallback}> <WrappedComponent {...props} /> </Suspense> ); } return Component; }
- 常规使用场景是包裹
React.lazy懒加载的组件:
const MyCompo = useMemo(() => lazy(() => import('path-to-component')), []);
- 存在的问题:当传入带多泛型参数的泛型组件时,封装后返回的组件会丢失原有的泛型能力。例如传入如下定义的组件:
type SomeTypeDefinition<T, R> = { typeValue?: T; returnValue?: R; someValue?: string; }; export default function DummyComponent<T, R>(props: SomeTypeDefinition<T, R>) { return ( <p> DummyComponent <b>{props.someValue}</b> </p> ); }
原生DummyComponent支持<DummyComponent<TType, RType> />的显式泛型传参用法,类型提示可以正常识别typeValue、returnValue的自定义类型;但经过useSuspense封装后,返回的组件无法支持该用法,泛型参数会被固化为宽泛类型。
问题根因
原实现的泛型参数P是在调用useSuspense的时刻就被静态推导为固定类型,内部返回的Component是props类型固定为P的非泛型组件,相当于把原泛型组件的动态泛型入参能力直接抹除了,无法在组件使用阶段再传入自定义泛型参数。
改造方案
核心思路是不在封装层固化组件的props类型,直接透传原组件的完整类型签名,改造后的实现如下:
import { ComponentType, Suspense, ReactNode } from 'react'; function useSuspense<T extends ComponentType<any>>( WrappedComponent: T, fallback: ReactNode = null ) { const Component = (props: React.ComponentProps<T>) => { return ( <Suspense fallback={fallback}> <WrappedComponent {...props} /> </Suspense> ); }; // 保留原组件的调试标识 Component.displayName = `WithSuspense(${ WrappedComponent.displayName || WrappedComponent.name || 'Component' })`; // 直接断言为原组件类型,完整保留泛型签名 return Component as T; }
关键改动说明
- 泛型参数直接接收整个组件类型
T,而非提前固定props类型P,从源头避免泛型被静态固化 - 内部组件的props类型通过
React.ComponentProps<T>自动推导,兼容普通组件、泛型组件的所有props场景 - 返回值直接断言为原组件类型
T,原组件上的泛型参数、静态属性、ref支持等类型信息会被完整保留 - 补充了原实现缺失的
fallback入参,支持自定义加载态
使用示例
包裹普通泛型组件:
const SuspenseDummy = useSuspense(DummyComponent, <div>加载中...</div>); // 使用时完全保留泛型传参能力 function Demo() { return ( <SuspenseDummy<number, string> typeValue={123} // 自动推导为number类型,类型提示正常 returnValue="test" // 自动推导为string类型,类型提示正常 someValue="示例" /> ); }
包裹lazy懒加载的泛型组件时,需要给lazy结果补充类型断言,避免导入阶段丢失泛型签名:
const LazyDummy = lazy(() => import('./DummyComponent').then(mod => ({ default: mod.default })) ) as typeof DummyComponent; // 断言为原组件的完整泛型类型 const LazySuspenseDummy = useSuspense(LazyDummy, <div>组件加载中</div>);
如果需要支持ref透传,可以在这个实现基础上配合
forwardRef做同逻辑的类型透传,核心原则就是不要在封装层把泛型组件的类型固化成固定props的普通组件。
内容的提问来源于stack exchange,提问作者danyhiol
相关产品推荐
相关产品推荐

