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
