Búsqueda de sitios web

TypeScript: Inferir el tipo de un objeto de interfaz genérica implícitamente a partir del contenido del objeto


Tengo un caso sencillo en TypeScript, donde tengo una interfaz con un tipo genérico T que extiende un enum E. Según el tipo se asigna un tipo de configuración.

enum E { "A", "B" }

type ConfigsBase = { [K in E]: object }

interface Configs extends ConfigsBase {
    [E.A]: { a: string };
    [E.B]: { b: number };
}

interface MyInterface<T extends E> {
  type: T;
  config: Configs[T];
}

La pregunta es cómo puedo usar esta interfaz de manera que el tipo genérico se infiera implícitamente a partir del objeto. Para que obtenga errores de tipo útiles cuando la configuración sea incorrecta.

const a: ??? = {
  type: E.A,
  config: { a: 1 }, 
}
// => I want this to give a typescript error because 1 is not a string

¿Qué necesito insertar en ??? para que TypeScript dé un error de que la configuración es incorrecta? Debería ser algo como const a: MyInterface<mira por ti mismo qué es T>.

Encontré una solución relativamente tediosa usando una función identidad que infiere el tipo automáticamente:

const inferType = <T extends E>(obj: MyInterface<T>): MyInterface<T> => obj

const a = inferType({
  type: E.A,
  config: { a: 1 },
})

// => TypeScript error: Type 'number' is not assignable to type 'string' 

¿Es posible hacer esto de manera más elegante con una anotación de tipo sin usar una función?

Respuesta #1

hay una manera con el operador infer. Ve https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-inference-in-conditional-types.

Puedes hacer esto:

enum E { "A", "B" }

type ConfigsBase = { [K in E]: object }

interface Configs extends ConfigsBase {
    [E.A]: { a: number };
    [E.B]: { b: number };
}

interface MyInterface<T extends E> {
  type: T;
  config: Configs[T];
}

type InferType<obj> = obj extends MyInterface<infer R> ? MyInterface<R> : never;

const variable = {
    type: E.A,
    config: { a: 1 }
};

const inferredVariable: InferType<typeof variable> = variable;

Será más rápido porque no usas funciones.

Patio de juegos actualizado

Respuesta #2

Usar el concepto de clase sellada es probablemente el enfoque más limpio y explícito.
Tampoco requiere tipos o funciones intermedias:

enum E { "A", "B" }

interface MyTypeBase<T extends E> {
  type: T;
}

interface MyTypeA extends MyTypeBase<E.A> {
  config: { a: string };
}

interface MyTypeB extends MyTypeBase<E.B> {
  config: { b: number };
}

type MyType = MyTypeA | MyTypeB;

const a: MyType = {
  type: E.A,
  config: { a: 1 }, // <-- will give type error as expected
};

En el Playground de TypeScript