Búsqueda de sitios web

Typescript - estrechando una unión de tipos a partir de los tipos de retorno de una función


¿Por qué es que TypeScript no puede discriminar una unión de tipos cuyos tipos provienen de los tipos de retorno de una función, sin declarar explícitamente el tipo de retorno en la función?

Aquí no especifico los valores de retorno de las funciones creadoras de eventos y el tipo unión no se puede precisar.

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

Pero si especifico los tipos de retorno de la siguiente manera, entonces se puede discriminar la unión.

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

Aquí hay un ejemplo en el playground de TS.

Respuesta #1

Porque TypeScript no infiere la constante como el tipo por defecto: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions

Por ejemplo:

var a = 'test'

Typescript inferirá el tipo de a como string, no como 'test'.

Puedes solucionarlo usando as const:

var a = 'test' as const;

En este caso a será del tipo 'test'.

Lo mismo para tu código:

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

El tipo de retorno de la función es {type: EventType} en lugar de {type:'foo'}.

Agregar as const al tipo de retorno funcionará como esperas en 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
      }
    }
  }
}

Artículos relacionados