Búsqueda de sitios web

typescript - Obtener nombres de campos de propiedades por tipo


Necesito obtener todas las propiedades de tipo number de un tipo:

export interface ApplicationQuote {
    readonly chronoUnit: ApplicationQuote.ChronoUnitEnum;
    readonly downloadedDocs: number;
    readonly downloadedKb: number;
    readonly uploadedKb: number;
    readonly uploadedRefs: number;
}
export namespace ApplicationQuote {
    export type ChronoUnitEnum = 'HOUR' | 'DAY' | 'MONTH' | 'YEAR';
    export const ChronoUnitEnum = {
        HOUR: 'HOUR' as ChronoUnitEnum,
        DAY: 'DAY' as ChronoUnitEnum,
        MONTH: 'MONTH' as ChronoUnitEnum,
        YEAR: 'YEAR' as ChronoUnitEnum
    };
}

Necesito obtener un Array<string> como ["downloadedDocs", "downloadedKb", "uploadedKb", "uploadedRefs"].

He probado este código:

let names = Object.getOwnPropertyNames(ApplicationQuote);
let keys = Object.keys(ApplicationQuote);

Respuesta #1

Voy a renombrar tu interfaz ApplicationQuote a IApplicationQuote, para distinguirla del espacio de nombres ApplicationQuote. Un problema importante que encontrarás de inmediato es que no puedes hacer algo como Object.keys(IApplicationQuote):

Object.keys(IApplicationQuote); // error!
// 'IApplicationQuote' only refers to a type, but is being used as a value here.

Una interfaz es una parte del sistema de tipos de TypeScript, que se elimina por completo del JavaScript emitido. Las interfaces solo existen en tiempo de diseño, mientras escribes el programa, y pueden ser verificadas en tiempo de compilación, cuando se emite el JavaScript... pero desaparecen en tiempo de ejecución. Así que no hay nada llamado IApplicationQuote en tiempo de ejecución que procesar.

Por lo tanto, necesitarás escribir algo como:

const numericProps = [
  "downloadedDocs",
  "downloadedKb",
  "uploadedKb",
  "uploadedRefs"
] as const;

(El as const es una aserción const que permite al compilador tratar numericProps como una tupla de literales de cadena en lugar de solo como un string[]).

Hay algunas formas de proceder desde aquí, entonces. Una es mantener tu interfaz y usar el compilador para ayudar a asegurar que tu valor numericProps tenga todos y solo los miembros correctos. Primero podemos definir KeysMatching<T, V>, que evalúa a una unión de claves en T que son asignables al tipo V:

type KeysMatching<T, V> = NonNullable<
  { [K in keyof T]: T[K] extends V ? K : never }[keyof T]
>;

Y úsalo así:

type NumericProps = KeysMatching<IApplicationQuote, number>;
// type NumericProps = "downloadedDocs" | "downloadedKb" | "uploadedKb" | "uploadedRefs"

Armados con el tipo NumericProps, podemos escribir esto:

type MutuallyAssignable<T extends U, U extends V, V = T> = true;

type NumericPropsOkay = MutuallyAssignable<
  typeof numericProps[number],
  NumericProps
>; // okay

Puedes usar MutuallyAssignable<T, U> para asegurar que los tipos que pases para T y U sean equivalentes; si no, obtendrás un error (así que MutuallyAssignable<string, string | "a"> está bien, pero MutuallyAssignable<number, number | "a"> no lo está). Dado que NumericPropsOkay no genera un error, sabes que no has cometido un error con numericProps.

Considera lo que sucede si cambias numericProps así:

const numericProps = [
  "downloadedDocs",
  "downloadedKb",
  "uploadedkb", // <-- note the typo
  "uploadedRefs"
] as const;

Entonces esto ocurrirá:

type NumericPropsOkay = MutuallyAssignable<
  typeof numericProps[number], // error!
  NumericProps
>;
// Type '"uploadedkb"' is not assignable to type 
// '"downloadedDocs" | "downloadedKb" | "uploadedKb" | "uploadedRefs"'.

Así que puedes usar esto para asegurarte de que tu arreglo escrito a mano y la definición de tu interfaz no se desvíen en el futuro.

La otra forma de proceder es definir tu interfaz en términos del valor numericProps. Es decir, hacer lo contrario de lo que pediste... JavaScript no sabe nada acerca de TypeScript, pero TypeScript sí sabe acerca de JavaScript. Así que puedes tomar esto:

const numericProps = [
  "downloadedDocs",
  "downloadedKb",
  "uploadedKb",
  "uploadedRefs"
] as const;

Y úsalo para definir esta interfaz:

export interface IApplicationQuote
  extends Readonly<Record<typeof numericProps[number], number>> {
  readonly chronoUnit: ApplicationQuote.ChronoUnitEnum;
}

Aquí hemos definido IApplicationQuote para extender Readonly<Record<typeof numericProps[number], number>>, que es esencialmente lo mismo que {readonly downloadedDocs: number, readonly downloadedKb: number, ...}. Solo tuvimos que añadir la única propiedad no numérica allí. Y puedes asegurarte de que funciona:

const appQuote: IApplicationQuote = {
  chronoUnit: "DAY",
  downloadedDocs: 1,
  downloadedKb: 2,
  uploadedKb: 3,
  uploadedRefs: 4
}; // okay

Y puedes verificar que IApplicationQuote sigue siendo el tipo que estás buscando:

const badAppQuote: IApplicationQuote = {
  chronoUnit: "DAY",
  downloadedDocs: 1,
  downloadedKb: 2,
  uploadedkb: 3, // error!
  uploadedRefs: 4
}; 
// 'uploadedkb' does not exist in type 'IApplicationQuote'. 
// Did you mean to write 'uploadedKb'?

Se ve bien.

Está bien, espero que eso ayude; ¡buena suerte!

Enlace al código

Artículos relacionados