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

如何让泛型函数可省略未定义类型的属性与参数?

问题描述

我希望实现一个泛型类,其中的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
    }
  }
}

验证效果

现在测试之前的使用场景:

  1. 必填字段场景:
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}})
}
  1. 无参调用场景:
const instance1 = new MyGenericClass<never, Config<never, never>>()
instance1.method() // 正常通过

const instance2 = new MyGenericClass()
instance2.method() // 正常通过

关键说明

  • RequiredKeys和OptionalKeys通过条件类型筛选出需要设为必填/可选的字段;
  • IsAllOptional判断参数类型是否没有必填字段,以此决定方法参数是否可以省略;
  • 给泛型参数设置默认值never,让无参实例化时默认满足无参调用的条件。

内容的提问来源于stack exchange,提问作者Alan Donizete

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 09:25:14