Typescript - 从函数 return 类型缩小类型联合

Typescript - narrowing a type union from function return types

为什么 Typescript 无法区分类型来自 return 类型函数的类型联合,而无需在函数上显式声明 return 类型?

这里我没有指定事件创建函数的return值,联合类型不能缩小。

enum EventType {
  FOO = "foo",
  GOO = "goo",
}

function createFooEvent(args: {
  documentId: number | null
}) {
  return {
    type: EventType.FOO,
    data: args
  }
}
function createGooEvent(args: {
  id: number
  isSelected: boolean
}) {
  return {
    type: EventType.GOO,
    data: args
  }
}

type EventArgType =
  | ReturnType<typeof createFooEvent>
  | ReturnType<typeof createGooEvent>

function eventHandler(event: EventArgType) {
  switch(event.type) {
    case EventType.FOO: {
      // Note that `event` contains `data` but `data`'s type is a union and has not been discriminated
      event.data;
      break
    }
  }
}

但是如果我如下指定 return 类型,则联合可以被区分。

function createFooEvent(args: {
  documentId: number | null
}): {
  type: EventType.FOO,
  data: {
    documentId: number | null
}} {
  return {
    type: EventType.FOO,
    data: args
  }
}
function createGooEvent(args: {
  id: number
  isSelected: boolean
}): {
  type: EventType.GOO,
  data: {
    id: number
    isSelected: boolean
}} {
  return {
    type: EventType.GOO,
    data: args
  }
}

Here is an example in TS playground.

因为打字稿默认情况下不会将常量推断为类型:https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions

例如:

var a = 'test'

Typescript 会将 a 的类型推断为 string 而不是 'test'.

您可以使用 as const 修复它:

var a = 'test' as const;

在这种情况下 a 的类型为 'test'

您的代码也一样:

  function createFooEvent(args: {
    documentId: number | null
  }) {
    return {
      type: EventType.FOO,
      data: args
    };
  }

函数的return类型是{type: EventType}而不是{type:'foo'}

as const 添加到 return 类型,将按您预期的方式工作 TS Playground

function exampleOne(){
  enum EventType {
    FOO = "foo",
    GOO = "goo",
  }

  function createFooEvent(args: {
    documentId: number | null
  }) {
    return {
      type: EventType.FOO,
      data: args
    } as const;
  }
  function createGooEvent(args: {
    id: number
    isSelected: boolean
  }) {
    return {
      type: EventType.GOO,
      data: args
    } as const;
  }

  type EventArgType =
    | ReturnType<typeof createFooEvent>
    | ReturnType<typeof createGooEvent>

  function eventHandler(event: EventArgType) {
    switch(event.type) {
      case EventType.FOO: {
        // event.data in this case will be {documentId: number|null}
        event.data;
        break
      }
    }
  }
}