简体   繁体   English

TypeScript区分了与泛型的联合

[英]TypeScript discriminated union with generics

Is there a way to set up a discriminated union that lets you capture a specific type with each union member? 有没有一种方法可以建立一个区分工会,让您与每个工会成员一起捕获特定类型? I'm trying to write a type safe command handler along the lines of: 我试图按照以下方式编写类型安全的命令处理程序:

interface GetUsersCommand { // returns User[]
  type: 'GET_USERS'
}

interface UpdateUserNameCommand { // returns void
  type: 'UPDATE_USER_NAME'
  userId: string
  name: string
}

type Command<Result> = GetUsersCommand | UpdateUserNameCommand

class CommandExecutor {
  execute<Result>(command: Command<Result>): Result {
    switch (command.type) {
      case 'GET_USERS': return [ user1, user2, user3 ]
      case 'UPDATE_USER_NAME': updateUserName(command.userId, command.name)
    }
  }
}

The idea is that the CommandExecutor would not only knows the fields in each command after narrowing, but could also verify that the return type is as required for each command. 这个想法是, CommandExecutor不仅会在缩小范围后知道每个命令中的字段,而且还可以验证返回类型是否符合每个命令的要求。 Is there a good pattern to achieve this in TypeScript? 有没有好的模式可以在TypeScript中实现呢?

You can create a relation between the command type and the result type by using an overload that captures the passed in command type and a mapping interface (you could also use a result union, but extracting the result type would not cause errors for missing command types, so I favor the mapping interface in this case). 您可以通过使用捕获捕获的传入命令类型的重载和映射接口来在命令类型和结果类型之间创建关系(也可以使用结果联合,但是提取结果类型不会因缺少命令类型而导致错误,因此在这种情况下,我更喜欢映射接口。

We can't ensure the return type in the implementation corresponds to the expected return type directly. 我们无法确保实现中的返回类型直接对应于预期的返回类型。 The best we can do is use an extra function to validate this in the switch : 我们能做的最好的就是在开关中使用一个额外的功能来验证这一点:


interface GetUsersCommand { // returns User[]
  type: 'GET_USERS'
}

interface CommandResults {
  'GET_USERS': Users[]
}

interface UpdateUserNameCommand { // returns void
  type: 'UPDATE_USER_NAME'
  userId: string
  name: string
}
interface CommandResults {
  'UPDATE_USER_NAME': void
}
type Command = GetUsersCommand | UpdateUserNameCommand

function checkResult<T extends  keyof CommandResults>(type: T, result: CommandResults[T])  {
  return result;
}

class CommandExecutor {
  execute<T extends Command>(command: T): CommandResults[T['type']]
  execute(command: Command): CommandResults[keyof CommandResults] {
    switch (command.type) {
      case 'GET_USERS': return checkResult(command.type, [ user1, user2, user3 ])
      case 'UPDATE_USER_NAME': return checkResult(command.type, updateUserName(command.userId, command.name))
    }
  }
}

Playground 操场

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM