Búsqueda de sitios web

typescript - Desestructuración de unión discriminada


Estoy tratando de escribir código que funcione con uniones discriminadas genéricas con la propiedad type.

Digamos que tengo un montón de uniones discriminadas como:

interface IFoo {
  type: "foo";
  foo: number;
}

interface IBar {
  type: "bar";
  bar: number;
}

interface IBaz {
  type: "baz";
  baz: number;
}

type IObject = IFoo | IBar | IBaz;

La primera tarea que resolví es determinar los posibles valores de la propiedad tipo:

declare let _object: IObject;
type ObjectType = typeof _object.type;

(por cierto, ¿hay alguna manera de hacer lo mismo sin declarar nada extra?)

Necesito declarar un tipo genérico para ser usado así:

Case<IObject, "foo"> // = IFoo
Case<IObject, "bar"> // = IBar

para que pueda declarar:

function filter<Type extends ObjectType>(
    objects: IObject[], 
    type: Type,
): Case<IObject, type>[] {
  return objects.filter((o) => o.type == type);
}

¿Es esto posible?

Respuesta #1

Sí, es posible

interface IFoo {
  type: "foo";
  foo: number;
}

interface IBar {
  type: "bar";
  bar: number;
}

interface IBaz {
  type: "baz";
  baz: number;
}

type IObject = IFoo | IBar | IBaz;

type TypeSwitch<N extends string, T extends { type: N }> =
       { [n in N]: T extends { type: n } ? T : never };

type Case<T extends { type: string }, N extends T['type']> =
       TypeSwitch<T['type'], T>[N];

type F = Case<IObject, "foo">; // = IFoo
type B = Case<IObject, "bar">; // = IBar

Además, puedes referirte a un tipo de propiedad usando el operador de tipo 'consulta de tipo indexado' (en realidad la misma sintaxis que el acceso a propiedades con [], solo que opera sobre tipos)

type ObjectType = IObject['type'];

Finalmente, usar todo lo anterior en filter da el tipo de elementos del array filtrado como se esperaba:

function filter<Type extends ObjectType>(
    objects: IObject[], 
    type: Type,
): Case<IObject, Type>[] {
  return objects.filter((o) => o.type == type);
}


let o: IObject[];
const a = filter(o, 'bar'); // inferred as const a: IBar[]