TypeScript 区分联合与泛型

TypeScript discriminated union with generics

有没有办法设置一个可区分的联合,让您捕获每个联合成员的特定类型?我正在尝试按照以下行编写类型安全的命令处理程序:

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)
    }
  }
}

想法是 CommandExecutor 不仅可以知道缩小后每个命令中的字段,还可以验证 return 类型是否符合每个命令的要求。在 TypeScript 中是否有一个很好的模式来实现这一点?

您可以通过使用捕获传入命令类型的重载和映射接口来创建命令类型和结果类型之间的关系(您也可以使用结果联合,但提取结果类型不会导致缺少命令类型的错误,所以在这种情况下我更喜欢映射接口)。

我们无法确保实现中的 return 类型直接对应于预期的 return 类型。我们能做的最好的事情就是在开关中使用一个额外的函数来验证这一点:


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