网站搜索

Typescript:通过检查字符串值来推断泛型类型


我有这样的类型:

export type NotificationType = 'comment' | 'like' | 'message' | 'follow' | unknown

type MessageBody<T extends NotificationType> = T extends 'comment'
  ? {
      body: string
      threadId: string
      projectId: string
    }
  : T extends 'like'
  ? {
      resourceId: string
      resourceType: 'project' | 'projectVersion' | 'thread' | 'comment'
    }
  : T extends 'message'
  ? {
      body: string
      chatId: string
    }
  : T extends 'follow'
  ? {}
  : never

export type Message<T extends NotificationType = unknown> = {
  channel: string
  message: MessageBody<T> & {
    type: T
  }
  timetoken: string | number
  uuid?: string
  publisher?: string
}

我现在想做的是通过检查属性 message.type 来推断 T 的类型,而不必事先指定 T 的类型,所以像这样:

function checkMessage(message: Message) { // No generic type given
  switch (message.message.type) {
    case 'comment':
      message.message.body // infers to MessageBody<'comment'>
  }
}

但是使用这种方法,我在 switch 语句上收到一个错误:类型 'never' 上不存在属性 'type'。

这可能来自于在 NotificationType 中使用 unknown。我如何在保持泛型可选的同时,通过检查字符串来推断类型?

答案#1

我敢打赌 NotificationType 应该只是一个字符串。因此,使用 unknown 来表示未知的字符串类型并不是最好的主意。使用普通的 string 类型要好得多。然而,将 string 类型与其他字面量字符串如 comment 和 follow 一起使用,会将它们都混为一个 string。这就是为什么使用 string & {} 更好的原因。

此外,我使用了映射数据结构而不是条件类型 MessageBody。

export type KnownType = 'comment' | 'like' | 'message' | 'follow'

export type UnknownType = string & {}

type NotificationType = KnownType | UnknownType

type MessageMap = {
  comment: {
    body: string
    threadId: string
    projectId: string
  },
  like: {
    resourceId: string
    resourceType: 'project' | 'projectVersion' | 'thread' | 'comment'
  },
  message: {
    body: string
    chatId: string
  },
  follow: {}
}

type MessageBase = {
  channel: string
  timetoken: string | number
  uuid?: string
  publisher?: string
}

type MessageBody<T extends KnownType> = { message: MessageMap[T] & { type: T } } & MessageBase

export type ComputeMessage<T extends NotificationType> = T extends KnownType ? MessageBody<T> : never

function checkMessage<MessageType extends NotificationType>(message: ComputeMessage<MessageType>) {

  switch (message.message.type) {
    case 'comment':
      message.message.projectId // ok
      message.message.threadId // ok
  }
}

操场