如何让泛型函数可省略未定义类型的属性与参数?
问题描述
我希望实现一个泛型类,其中的method方法参数能根据泛型类型动态决定必填项:当MyObject、TypeA或TypeB不为undefined或never时,对应的obj、typeA、typeB字段为必填;当这些类型为undefined或never时,字段可选,甚至可以直接无参调用方法。
当前代码使用Partial导致所有字段都是可选的,无法满足需求:
type Config<TypeA extends object | undefined, TypeB extends object | undefined> = { typeA: TypeA, typeB: TypeB } type Parameter<MyObject extends object, MyConfig extends Config<any, any>> = Partial<MyConfig & { obj: MyObject }> type GenericClassResponse<MyObject extends object | undefined> = { obj?: MyObject } class MyGenericClass<_MyObject extends object | undefined, MyConfig extends Config<any, any>> { constructor(private config?: Partial<MyConfig>) {} // 为了通过编译将参数设为`value?`,但希望当MyObject、TypeA或TypeB不为undefined时,该参数必填 method(values?: Parameter<MyObject, MyConfig>): GenericClassResponse<MyObject> { return { obj: values?.obj } } }
期望的使用场景:
type MyObject = { a: number } type OtherType = { c: boolean } function useMyGenericClassWithValues() { const instance = new MyGenericClass<MyObject, Config<OtherType, undefined>>({}) instance.method({obj: {a: 0}, typeA: {c: true}}) // 此处obj和typeA应为必填 } // 无参调用场景 const instance1 = new MyGenericClass<never, Config<never, never>>() instance1.method() const instance2 = new MyGenericClass() instance2.method()
解决方案
要实现这种动态控制必填项的效果,我们可以通过条件类型和工具类型构造参数类型,并根据参数类型是否包含必填字段决定方法参数是否可选。
步骤1:定义工具类型
先写几个工具类型,用来区分必填字段和可选字段:
// 提取类型中类型不为undefined/never的字段(这些需要设为必填) type RequiredKeys<T> = { [K in keyof T]: T[K] extends undefined | never ? never : K }[keyof T] // 提取类型中类型为undefined/never的字段(这些设为可选) type OptionalKeys<T> = { [K in keyof T]: T[K] extends undefined | never ? K : never }[keyof T] // 判断一个类型是否所有字段都是可选的 type IsAllOptional<T> = T extends Partial<T> ? true : false
步骤2:重构Parameter类型
基于上面的工具类型,重构Parameter类型,让字段根据泛型类型动态切换必填/可选:
type Parameter<MyObject extends object | undefined, MyConfig extends Config<any, any>> = // 保留必填字段(非undefined/never的类型) Pick<{ obj: MyObject } & MyConfig, RequiredKeys<{ obj: MyObject } & MyConfig>> & // 包装可选字段(undefined/never的类型) Partial<Pick<{ obj: MyObject } & MyConfig, OptionalKeys<{ obj: MyObject } & MyConfig>>>
步骤3:调整method方法的参数类型
通过IsAllOptional判断参数类型是否全为可选,以此决定方法参数是否可以省略:
type GenericClassResponse<MyObject extends object | undefined> = { obj?: MyObject } class MyGenericClass<MyObject extends object | undefined = never, MyConfig extends Config<any, any> = Config<never, never>> { constructor(private config?: Partial<MyConfig>) {} method( values: IsAllOptional<Parameter<MyObject, MyConfig>> extends true ? Parameter<MyObject, MyConfig> | undefined : Parameter<MyObject, MyConfig> ): GenericClassResponse<MyObject> { return { obj: values?.obj } } }
验证效果
现在测试之前的使用场景:
- 必填字段场景:
type MyObject = { a: number } type OtherType = { c: boolean } function useMyGenericClassWithValues() { const instance = new MyGenericClass<MyObject, Config<OtherType, undefined>>({}) // 必须传入obj和typeA,否则会触发编译错误 instance.method({obj: {a: 0}, typeA: {c: true}}) // 错误示例:缺少obj会报错 // instance.method({typeA: {c: true}}) }
- 无参调用场景:
const instance1 = new MyGenericClass<never, Config<never, never>>() instance1.method() // 正常通过 const instance2 = new MyGenericClass() instance2.method() // 正常通过
关键说明
RequiredKeys和OptionalKeys通过条件类型筛选出需要设为必填/可选的字段;IsAllOptional判断参数类型是否没有必填字段,以此决定方法参数是否可以省略;- 给泛型参数设置默认值
never,让无参实例化时默认满足无参调用的条件。
内容的提问来源于stack exchange,提问作者Alan Donizete
相关产品推荐
相关产品推荐

