如何在React高阶组件中保留泛型类型
React高阶组件保留泛型的实现方案
场景说明
使用React高阶组件模式时,需要在Props传入目标组件前对部分Props做转换处理:
- 上游传入的Props中
foo、bar为可选字段 - 转换后传入内部组件的下游Props中
foo、bar、bix为必填字段 - 内部组件本身是泛型组件,包装后需要保留泛型能力
基础类型与转换逻辑如下:
type UpstreamProps<X> = { foo?: number bar?: string baz: X } type DownstreamProps<X> = { foo: number bar: string baz: X bix: string } function transform<X>(props: UpstreamProps<X>): DownstreamProps<X> { return { ...props, foo: props.foo || 0, bar: props.bar || '(unknown)', bix: `${props.foo}-${props.bar}-bix` } }
内部组件使用严格的下游Props类型定义:
function Parent<X>(props: DownstreamProps<X> & {formatter: (x: X) => string}) { return ( <> foo = {props.foo} bar = {props.bar} baz = {props.formatter(props.baz)} bix = {props.bix} </> ) }
调用包装后的组件时,只需要传入上游可选Props,同时支持指定泛型参数:
<WrappedParent<number> baz={22} formatter={(x: number) => `my number squared is ${Math.pow(x, 2)}`} />
初始写法的问题在于HOC调用时就把泛型参数固化,导致包装后的组件丢失泛型能力,同时需要同时满足两个约束:
- 传入不符合要求的组件(Props不包含DownstreamProps所有字段)给
withUpstreamProps时,必须抛出类型错误 - 包装后的组件支持单泛型参数调用,如
WrappedParent<number>
最终实现
import React from 'react'; function withUpstreamProps<P extends DownstreamProps<any>>( WrappedComponent: React.ComponentType<P> ) { // 返回独立的泛型函数组件,不在HOC调用阶段固化泛型 const Wrapped = <X,>( props: Omit<P, keyof DownstreamProps<X>> & UpstreamProps<X> ) => { const { foo, bar, baz, ...rest } = props; const transformedProps = transform({foo, bar, baz}); return <WrappedComponent {...transformedProps} {...(rest as any)} /> }; return Wrapped; } // 直接包装即可,不需要额外传泛型 const WrappedParent = withUpstreamProps(Parent);
实现逻辑说明
- 入参合法性校验:通过
P extends DownstreamProps<any>约束传入的组件必须接收DownstreamProps的所有字段,传入不相关的随机组件时TypeScript会直接报类型错误,满足第一个约束。 - 泛型能力保留:返回的包装组件本身是独立泛型组件,泛型参数
X在组件调用阶段才会被推导,不会在HOC执行阶段被固化,因此支持WrappedParent<number>这类单泛型调用写法。 - 类型自动合并:通过
Omit<P, keyof DownstreamProps<X>> & UpstreamProps<X>自动计算包装后组件的Props类型:将原组件中属于下游Props的部分替换为上游可选Props,组件自带的其他自定义Props(比如示例中的formatter)会完整保留,类型推导完全自动,不需要手动声明额外类型。
提示:如果使用TypeScript 4.7以下版本,泛型箭头函数需要在泛型参数后加逗号(即
<X,>写法)避免和JSX语法冲突,高版本可直接写<X>。
内容的提问来源于stack exchange,提问作者Sasgorilla
相关产品推荐
相关产品推荐

