假设我想要在单个函数中管理来自项目中不同模块的服务,根据我获得的数据类型,我调用不同的服务.

例如,我有两个DTO:

class UserDto {
  constructor (name: string) {
      this.name = name
  }
  public name: string;
}

class CarDto {
  constructor (brand: string) {
      this.brand = brand
  }
  public brand: string;
}

和这两项服务:

class UserService {
  create(val: UserDto) {
    // Create the user
    return 'foo';
  }
}

class CarService {
  create(val: CarDto) {
    // Create the car
    return 'bar';
  }
}

很基本的东西.然后,我可以获得一个服务,该服务可以根据我获得的数据类型来管理呼叫.为此,我创建了一个联合类型,该类型应该计算出我要获取的数据类型以及我应该调用什么服务,假设是这样的:

type DataType =
  | {
      data: 'user';
      dto: UserDto;
      service: 'userService'
    }
  | {
      data: 'car';
      dto: CarDto;
      service: 'carService'
    }

最后,我想将所有这些放在管理这两个子服务的服务中的一个函数中,如下所示:

class MainService {
  constructor (
    private userService: UserService,
    private carService: CarService,
  ) {}

  serviceManager(val: DataType) {
   // Call the right service here!
  }
}

我不想做的是判断每一处房产,如果那是我想要的.This is what I'd like to avoid:

serviceManager(val: DataType) {
   const name = val.data;
    if (name === 'user') this.userService.create(val.dto);
  }

相反,我更喜欢这样一种方法,即通过打字来推断我得到了什么服务,以及我正在向所述服务传递什么信息.因此,这就是I thought应该起作用的地方(剧透,它没有):

serviceManager(val: DataType) {
   const func = this[val.service];
    func.create(val.dto);
    // Argument of type 'UserDto | CarDto' is not assignable to parameter of type 'UserDto & CarDto'.
    // Type 'UserDto' is not assignable to type 'UserDto & CarDto'.
    // Property 'brand' is missing in type 'UserDto' but required in type 'CarDto'.
  }

最后,我决定走另一条路,甚至不使用子服务.但我一直在试图找到解决这个问题的方法,即使它与我正在工作的项目无关.

是否有可能通过某种不同的方式声明类型dataType来使用TypeScrip实现类似的操作,或者这完全是不可能的?也许我应该在声明子服务中的函数时使用泛型?

推荐答案

TypeScrip不直接支持我所说的"相关union types".在您的serviceManager()实现中,val参数是联合类型DataType.因此,this[val.service]val.dto也是联合类型(分别为UserService | CarServiceUserDto | CarDto).但是TypeScrip没有意识到,也不能跟踪到这些联合以这样一种方式"相关",例如,如果this[val.service]是类型UserService,则val.dto将是类型UserDto.编译器认为可能有this[val.service]将是类型UserService,而val.dto将是类型CarDto.然后你就会得到一个错误.这个一般性的问题在microsoft/TypeScript#30581中讨论过.

microsoft/TypeScript#47109中详细介绍了处理此问题的推荐方法.您的代码应该重构为使用一些基本的键-值映射类型,generic indexes into这些类型,以及基于这些类型的泛型索引mapped types,而不是使用相关的联合.对于您的代码,我倾向于这样重构它:

interface DtoMap {
  user: UserDto,
  car: CarDto
}

type ServiceMap =
  { [K in keyof DtoMap]: { create(val: DtoMap[K]): string } }

type DataType<K extends keyof DtoMap> = { [P in K]:
  { data: P, dto: DtoMap[P] }
}[K]

DtoMap类型是您需要的"基本键-值映射类型",它从您想要的DataType联合的data属性映射到相应的DTO类型.

然后,ServiceMap描述对应于这些DTO类型的服务对象.

DataType<K>是一个distributive object type,因此DataType<keyof DtoMap>是一个类似于原始DataType类型的联合(除了我们不需要service来居住在这里;我们只需要编写一个ServiceMap类型的services对象).如果您将单个密钥作为K传递,则只会得到DataType联合中的该成员.

现在,我们可以将K constrained中的serviceManager()泛型化为DtoMap的键:

serviceManager<K extends keyof DtoMap>(val: DataType<K>) {
  const services: ServiceMap = {
    car: this.carService,
    user: this.userService
  }
  const func = services[val.data];
  func.create(val.dto);
}

val的输入类型为DataType<K>,这意味着val.data将确定呼叫属于哪个联盟成员.services对象可证明为类型ServiceMap,因此const func = services[val.data]被视为泛型类型{ create(val: DtoMap[K]): string }.这意味着func.create(val: DtoMap[K]) => string型的.由于val.dto属于DtoMap[K]类型,这意味着您可以拨打func.create(val.dto).

Playground link to code

Typescript相关问答推荐

如何根据数据类型动态注入组件?

如果我有对象的Typescript类型,如何使用其值的类型?

在动态对话框中使用STEP组件实现布线

如何提取密钥及其对应的属性类型,以供在新类型中使用?

使某些(嵌套)属性成为可选属性

如何按不能保证的功能过滤按键?

有条件地删除区分的联合类型中的属性的可选属性

如何使我的函数专门针对联合标记?

在排版修饰器中推断方法响应类型

在Mac和Windows上运行的Web应用程序出现这种对齐差异的原因是什么?(ReactNative)

如何过滤文字中的类对象联合类型?

是否有可能避免此泛型函数体中的类型断言?

类型';字符串|数字';不可分配给类型';未定义';.类型';字符串';不可分配给类型';未定义';

如何获取受类型脚本泛型约束的有效输入参数

从route.参数获取值.订阅提供";无法读取未定义";的属性

通过函数传递确切的类型,但验证额外的字段

如何避免TS2322;类型any不可分配给类型never;使用索引访问时

如何从联合类型推断函数参数?

有没有办法从不同长度的元组的联合中提取带有类型的最后一个元素?

typeof 运算符和泛型的奇怪行为