keyof prend un type objet et produit l'union de ses noms de propriétés. Ci-dessous, keyof User est le type "id" | "name" | "email" : une variable de ce type ne peut donc contenir que l'une de ces trois chaînes.
keyof n'existe que dans le système de types. Il est effacé à la compilation et ne vérifie donc jamais rien à l'exécution. Ce qu'il vous apporte, c'est une garantie à la compilation qu'une clé désigne une vraie propriété : renommez email dans l'interface et chaque "email" typé keyof User devient une erreur.
keyof typeof : les clés d'un objet simple
keyof fonctionne sur des types, pas sur des valeurs. Pour un objet que vous avez écrit comme une valeur, obtenez d'abord son type avec typeof, puis prenez les clés :
Lisez de l'intérieur vers l'extérieur : typeof colors vaut { red: string; green: string; blue: string }, et le keyof de ce type est l'union des clés. Écrire keyof colors sans typeof échoue avec error TS2749: 'colors' refers to a value, but is being used as a type here. Did you mean 'typeof colors'?.
C'est la façon habituelle de dériver une union à partir d'une table de correspondance : ajoutez une couleur à l'objet et le type Color grandit avec lui. typeof a aussi un second sens, à l'exécution ; la page typeof présente les deux.
Accès typé aux propriétés avec les génériques
L'usage le plus courant de keyof est une contrainte générique. K extends keyof T signifie « K est l'une des clés de T », et T[K] est le type de la propriété à cette clé :
Le second paramètre de type compte. Avec key: keyof T et sans K, le type de retour est T[keyof T], l'union de tous les types de propriétés (ici string | number | boolean), donc title.toUpperCase() échouerait avec error TS2339: Property 'toUpperCase' does not exist on type 'string | number | boolean'. Capturer la clé exacte dans K conserve le type exact de la propriété. T[K] est un type d'accès indexé, décrit sur la page des types d'accès indexé.
Indexer avec un simple string
Une erreur très courante apparaît quand vous indexez un objet avec un string venu d'ailleurs :
Le compilateur affiche :
index.ts(10,10): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Settings'.
No index signature with a parameter of type 'string' was found on type 'Settings'.
N'importe quelle chaîne pourrait atteindre read, y compris "colour", et Settings ne dit rien de cette clé. Deux corrections : n'accepter que les vraies clés, ou vérifier une chaîne inconnue avant de l'utiliser.
name is keyof Settings est un prédicat de type : quand la fonction renvoie true, TypeScript traite input comme une clé dans le if. La page sur les type guards explique les prédicats en détail.
keyof avec les signatures d'index : string | number
Pour un type doté d'une signature d'index, keyof renvoie le type de l'index, et une signature d'index de type string donne string | number, pas seulement string :
JavaScript convertit chaque clé de propriété numérique en chaîne, donc scores[42] et scores["42"] désignent la même propriété. TypeScript le modélise en incluant number. La sortie montre aussi une règle de JavaScript : les clés qui ressemblent à des entiers sont listées en premier, par ordre croissant, avant les autres clés de type chaîne. Quand une fonction générique n'a besoin que des clés de type string, écrivez Extract<keyof T, string> ou string & keyof T ; Exclude et Extract ont leur propre page.
Les résultats de keyof en un coup d'œil
| Type | keyof donne |
|---|---|
{ id: number; name: string } | "id" | "name" |
{ [key: string]: number } | string | number |
{ [index: number]: string } | number |
{} | never |
any | string | number | symbol |
{ a: 1; b: 2 } | { a: 3; c: 4 } | "a" (les clés que possède chaque membre) |
{ a: 1 } & { c: 4 } | "a" | "c" (les clés de l'un ou de l'autre) |
string[] | number plus le nom de chaque méthode de tableau ("length", "push", ...) |
Les lignes union et intersection semblent inversées au premier abord. Une valeur d'un type union peut être l'un ou l'autre membre, donc seules les clés présentes dans les deux sont sûres. Une valeur d'une intersection possède tout ce qu'ont les deux, elle a donc toutes les clés.
Pourquoi Object.keys renvoie string[]
Object.keys(obj) est typé string[], jamais (keyof T)[]. La raison est le typage structurel : une valeur peut porter plus de propriétés que son type déclaré.
p est typé Point, et pourtant il a une clé z à l'exécution. Si Object.keys promettait ("x" | "y")[], le code qui s'appuie dessus serait faux pour p. Le cast est sûr pour les littéraux objet que vous avez créés vous-même ; pour les objets venus de l'extérieur, gardez les clés en string et vérifiez-les.
keyof dans les types mappés
keyof permet aussi de parcourir les clés d'un type pour construire un nouveau type. C'est ainsi que sont écrits les types utilitaires intégrés Partial et Readonly :
type MyPartial<T> = { [K in keyof T]?: T[K] };
type MyReadonly<T> = { readonly [K in keyof T]: T[K] };
interface User {
id: number;
name: string;
}
type UserDraft = MyPartial<User>; // { id?: number; name?: string }
[K in keyof T] visite chaque clé, et T[K] lit le type de la propriété à cette clé. Les types mappés ont leur propre page, avec les modificateurs et le remappage des clés.
Questions fréquentes
Que fait keyof en TypeScript ?
keyof T produit une union des noms de propriétés du type T. Pour interface User { id: number; name: string }, keyof User vaut "id" | "name". Il ne fonctionne que sur des types, à la compilation, et ne produit aucun JavaScript.
Qu'est-ce que keyof typeof en TypeScript ?
keyof a besoin d'un type, mais un objet simple comme const colors = { red: "#f00" } est une valeur. typeof colors transforme la valeur en son type, puis keyof typeof colors donne l'union de ses clés, ici "red". Écrire keyof colors échoue avec l'erreur TS2749.
Pourquoi keyof renvoie-t-il string | number ?
Quand le type a une signature d'index de type string comme { [key: string]: number }. JavaScript convertit les clés de propriété numériques en chaînes, donc obj[42] est aussi un accès valide, et TypeScript inclut number dans le type des clés. Utilisez Extract<keyof T, string> ou string & keyof T si vous ne voulez que les clés de type string.
Pourquoi Object.keys renvoie-t-il string[] et non (keyof T)[] ?
Parce qu'un objet peut contenir plus de propriétés que ne le liste son type : une valeur avec un champ en plus peut être assignée à un type plus étroit. Typer le résultat en (keyof T)[] serait un mensonge pour ces objets. Si vous savez que l'objet a exactement les clés déclarées, faites un cast : Object.keys(obj) as (keyof typeof obj)[].
Comment corriger « Element implicitly has an 'any' type because expression of type 'string' can't be used to index type » ?
L'indice est un simple string, et le type de l'objet n'a pas de signature d'index de type string. Typez le paramètre en keyof YourType au lieu de string, ou vérifiez d'abord la chaîne avec un type guard qui l'affine en keyof YourType, ou donnez à l'objet une signature d'index ou un type Record<string, V> s'il accepte vraiment n'importe quelle clé.