Búsqueda de sitios web

javascript - Typescript crear interfaz dinámicamente


Uso simple-schema para definir esquemas de base de datos en un objeto:

{
   name: 'string',
   age: 'integer',
   ...
}

¿Es de alguna manera posible crear una interfaz o clase a partir de este objeto, para que no tenga que escribir todo dos veces?

Respuesta #1

Puedes hacer esto, pero podría ser más problema que beneficio a menos que creas que podrías estar cambiando el esquema. TypeScript no tiene formas integradas de inferir tipos de la manera que deseas, así que tienes que persuadirlo y convencerlo para que lo haga:

Primero, define una forma de asignar los nombres literales 'string' y 'integer' a los tipos de TypeScript que representan (presumiblemente string y number respectivamente):

type MapSchemaTypes = {
  string: string;
  integer: number;
  // others?
}

type MapSchema<T extends Record<string, keyof MapSchemaTypes>> = {
  -readonly [K in keyof T]: MapSchemaTypes[T[K]]
}

Ahora, si puedes tomar un objeto de esquema con el tipo apropiado como el que especificaste, y obtener de él el tipo asociado:

const personSchema = {name: 'string', age: 'integer'}; 
type Person = MapSchema<typeof personSchema>; // ERROR

Ups, el problema es que personSchema se está infiriendo como {name: string; age: string} en lugar del deseado {name: 'string'; age: 'integer'}. Puedes arreglar eso con una anotación de tipo:

const personSchema: { name: 'string', age: 'integer' } = { name: 'string', age: 'integer' }; 
type Person = MapSchema<typeof personSchema>; // {name: string; age: number};

Pero ahora parece que te estás repitiendo. Afortunadamente, hay una manera de obligarlo a inferir el tipo correcto:

function asSchema<T extends Record<string, keyof MapSchemaTypes>>(t: T): T {
  return t;
}
const personSchema = asSchema({ name: 'string', age: 'integer' }); // right type now
type Person = MapSchema<typeof personSchema>; // {name: string; age: number};

ACTUALIZACIÓN 2020-06: en versiones más recientes de TS puedes usar una aserción const para obtener el mismo resultado:

const personSchema = { name: 'string', age: 'integer' } as const;
type Person = MapSchema<typeof personSchema>;

¡Eso funciona!

Véalo en acción en el Playground de Typescript. Espero que eso ayude; ¡buena suerte!

Respuesta #2

No creo que puedas declarar interfaces dinámicas. Sin embargo, puedes crear un tipo para objetos con propiedades conocidas.

Puedes crear un objeto que asigne literales de cadena a tipos reales, por ejemplo 'integer' => number, pero eso no es relevante para la pregunta. No sé qué framework estás usando, pero el siguiente ejemplo funciona para un framework de aspecto similar: Mongoose.

usuarios.js

export const UserSchema = mongoose.Schema({
    name: String,
    value: Number
});

export const Users = mongoose.Model('users', UserSchema);

export type User = { [K in keyof typeof UserSchema]: any } ;

uso :

import { User, Users } from './user';

Users.find({}).exec((err: Error, res: User) => { ... })

El resultado devuelto debe tener las mismas claves que UserSchema, pero todos los valores se asignan a any, ya que todavía tendrías que mapear los literales de cadena a tipos.

Artículos relacionados