TypeScript discriminated union with generics

别等时光非礼了梦想. 提交于 2020-01-24 20:50:09

问题


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. Is there a good pattern to achieve this in TypeScript?


回答1:


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



来源:https://stackoverflow.com/questions/57266294/typescript-discriminated-union-with-generics

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!