可辨识联合

可辨识联合是 TypeScript 中的一种模式,结合了联合类型和字面量类型。通过一个公共的可辨识属性,可以在运行时区分不同的类型,实现类型安全的模式匹配。

基本概念

可辨识联合使用一个公共属性(称为可辨识标签)来区分联合类型中的不同类型。

interface Circle {
  kind: "circle"
  radius: number
}

interface Rectangle {
  kind: "rectangle"
  width: number
  height: number
}

type Shape = Circle | Rectangle

kind 属性是可辨识标签,每个类型都有不同的字面量值。

类型守卫

使用可辨识属性进行类型守卫。

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius * shape.radius
    case "rectangle":
      return shape.width * shape.height
  }
}

const circle: Circle = { kind: "circle", radius: 5 }
const rectangle: Rectangle = { kind: "rectangle", width: 10, height: 20 }

console.log(getArea(circle))
console.log(getArea(rectangle))

switch 语句中,TypeScript 根据可辨识属性缩小类型范围。

穷尽检查

使用 never 类型确保处理了所有情况。

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius * shape.radius
    case "rectangle":
      return shape.width * shape.height
    default:
      const _exhaustiveCheck: never = shape
      return _exhaustiveCheck
  }
}

如果添加新的形状类型但没有处理,会编译报错。

实际应用

可辨识联合常用于处理不同类型的消息或事件。

interface LoginEvent {
  type: "login"
  userId: number
  timestamp: number
}

interface LogoutEvent {
  type: "logout"
  userId: number
  reason: string
}

interface MessageEvent {
  type: "message"
  from: number
  to: number
  content: string
}

type AppEvent = LoginEvent | LogoutEvent | MessageEvent

function handleEvent(event: AppEvent): void {
  switch (event.type) {
    case "login":
      console.log(`用户 ${event.userId} 登录于 ${new Date(event.timestamp)}`)
      break
    case "logout":
      console.log(`用户 ${event.userId} 登出,原因: ${event.reason}`)
      break
    case "message":
      console.log(`消息从 ${event.from} 发送到 ${event.to}: ${event.content}`)
      break
  }
}

handleEvent({
  type: "login",
  userId: 1,
  timestamp: Date.now()
})

handleEvent({
  type: "message",
  from: 1,
  to: 2,
  content: "你好"
})

type 属性区分不同的事件类型,每个事件有不同的数据结构。

状态管理

可辨识联合在状态管理中非常有用。

type State =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: string }
  | { status: "error"; error: Error }

function render(state: State): string {
  switch (state.status) {
    case "idle":
      return "等待中..."
    case "loading":
      return "加载中..."
    case "success":
      return `数据: ${state.data}`
    case "error":
      return `错误: ${state.error.message}`
  }
}

console.log(render({ status: "idle" }))
console.log(render({ status: "loading" }))
console.log(render({ status: "success", data: "用户数据" }))
console.log(render({ status: "error", error: new Error("网络错误") }))

status 属性区分不同的状态,每个状态有特定的数据。

API 响应

可辨识联合可以表示不同类型的 API 响应。

type ApiResult<T> =
  | { success: true; data: T }
  | { success: false; error: { code: number; message: string } }

interface User {
  id: number
  name: string
}

async function fetchUser(id: number): Promise<ApiResult<User>> {
  if (id > 0) {
    return {
      success: true,
      data: { id, name: "张三" }
    }
  }
  return {
    success: false,
    error: { code: 404, message: "用户不存在" }
  }
}

async function main() {
  const result = await fetchUser(1)

  if (result.success) {
    console.log(`用户: ${result.data.name}`)
  } else {
    console.log(`错误 ${result.error.code}: ${result.error.message}`)
  }
}

main()

success 属性区分成功和失败的响应。

Redux Action

可辨识联合是 Redux 中定义 Action 的标准模式。

interface IncrementAction {
  type: "INCREMENT"
  payload: number
}

interface DecrementAction {
  type: "DECREMENT"
  payload: number
}

interface ResetAction {
  type: "RESET"
}

type CounterAction = IncrementAction | DecrementAction | ResetAction

function reducer(state: number, action: CounterAction): number {
  switch (action.type) {
    case "INCREMENT":
      return state + action.payload
    case "DECREMENT":
      return state - action.payload
    case "RESET":
      return 0
  }
}

console.log(reducer(10, { type: "INCREMENT", payload: 5 }))
console.log(reducer(10, { type: "DECREMENT", payload: 3 }))
console.log(reducer(10, { type: "RESET" }))

type 属性区分不同的 Action,每个 Action 有自己的 payload 类型。

小结

可辨识联合使用公共的可辨识属性区分联合类型中的不同类型。通过类型守卫可以安全地访问特定类型的属性。穷尽检查确保处理了所有情况。可辨识联合在处理事件、状态管理、API 响应和 Redux Action 等场景中非常有用,是实现类型安全模式匹配的重要模式。