Menu

keyof in TypeScript: ottenere le chiavi di un oggetto come tipo

keyof prende un tipo oggetto e ti dà l'unione dei nomi delle sue proprietà. Scopri keyof con le interfacce, keyof typeof per gli oggetti semplici, l'accesso tipizzato alle proprietà con i generics, le index signature (string | number), e perché Object.keys restituisce string[].

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

keyof prende un tipo oggetto e produce l'unione dei nomi delle sue proprietà. Qui sotto keyof User è il tipo "id" | "name" | "email", quindi una variabile di quel tipo può contenere solo una di queste tre stringhe.

keyof esiste solo nel sistema dei tipi. Viene cancellato quando il codice viene compilato, quindi non controlla mai niente a runtime. Ciò che ti dà è la garanzia, in fase di compilazione, che una chiave indichi una proprietà reale: rinomina email nell'interfaccia e ogni "email" tipizzato come keyof User diventa un errore.

keyof typeof: le chiavi di un oggetto semplice

keyof funziona sui tipi, non sui valori. Per un oggetto che hai scritto come valore, prima ottieni il suo tipo con typeof, poi prendi le chiavi:

Leggilo dall'interno verso l'esterno: typeof colors è { red: string; green: string; blue: string }, e il keyof di quel tipo è l'unione delle chiavi. Scrivere keyof colors senza typeof fallisce con error TS2749: 'colors' refers to a value, but is being used as a type here. Did you mean 'typeof colors'?.

È il modo abituale di ricavare un'unione da una tabella di lookup: aggiungi un colore all'oggetto e il tipo Color cresce con esso. typeof ha anche un secondo significato, a runtime; la pagina su typeof li spiega entrambi.

Accesso tipizzato alle proprietà con i generics

L'uso più comune di keyof è come vincolo generico. K extends keyof T dice "K è una delle chiavi di T", e T[K] è il tipo della proprietà a quella chiave:

Il secondo parametro di tipo è importante. Con key: keyof T e senza K, il tipo di ritorno è T[keyof T], l'unione di tutti i tipi delle proprietà (qui string | number | boolean), quindi title.toUpperCase() fallirebbe con error TS2339: Property 'toUpperCase' does not exist on type 'string | number | boolean'. Catturare la chiave esatta in K mantiene il tipo esatto della proprietà. T[K] è un indexed access type, descritto nella pagina sugli indexed access types.

Indicizzare con una semplice string

Un errore molto comune compare quando indicizzi un oggetto con una string arrivata da altrove:

Il compilatore stampa:

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'.

A read potrebbe arrivare qualsiasi stringa, compreso "colour", e Settings non dice niente su quella chiave. Due soluzioni: accettare solo chiavi reali, o controllare una stringa sconosciuta prima di usarla.

name is keyof Settings è un type predicate: quando la funzione restituisce true, TypeScript tratta input come una chiave dentro l'if. La pagina sui type guard spiega i predicati nel dettaglio.

keyof con le index signature: string | number

Per un tipo con una index signature, keyof restituisce il tipo dell'indice, e una index signature di tipo stringa dà string | number, non solo string:

JavaScript converte ogni chiave numerica di una proprietà in stringa, quindi scores[42] e scores["42"] sono la stessa proprietà. TypeScript lo modella includendo number. L'output mostra anche una regola di JavaScript: le chiavi che sembrano interi vengono elencate per prime, in ordine crescente, prima delle altre chiavi stringa. Quando una funzione generica ha bisogno solo di chiavi stringa, scrivi Extract<keyof T, string> o string & keyof T; Exclude ed Extract hanno una pagina tutta loro.

I risultati di keyof in sintesi

Tipokeyof dà
{ id: number; name: string }"id" | "name"
{ [key: string]: number }string | number
{ [index: number]: string }number
{}never
anystring | number | symbol
{ a: 1; b: 2 } | { a: 3; c: 4 }"a" (le chiavi che ha ogni membro)
{ a: 1 } & { c: 4 }"a" | "c" (le chiavi di entrambi)
string[]number più il nome di ogni metodo degli array ("length", "push", ...)

Le righe di unione e intersezione all'inizio sembrano al contrario. Un valore di un tipo unione potrebbe essere l'uno o l'altro membro, quindi sono sicure solo le chiavi presenti in entrambi. Un valore di un'intersezione ha tutto di entrambi, quindi ha tutte le chiavi.

Perché Object.keys restituisce string[]

Object.keys(obj) è tipizzato come string[], mai (keyof T)[]. Il motivo è la tipizzazione strutturale: un valore può portare più proprietà di quelle del suo tipo dichiarato.

p è tipizzato Point, eppure a runtime ha una chiave z. Se Object.keys promettesse ("x" | "y")[], il codice che ci fa affidamento sarebbe sbagliato per p. Il cast è sicuro per gli oggetti letterali che hai creato tu; per gli oggetti che arrivano dall'esterno, tieni le chiavi come string e controllale.

keyof nei mapped types

keyof è anche il modo per iterare sulle chiavi di un tipo e costruirne uno nuovo. È così che sono scritti gli utility types integrati Partial e 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] visita ogni chiave, e T[K] legge il tipo della proprietà a quella chiave. I mapped types hanno una pagina tutta loro, con modificatori e rimappatura delle chiavi.

Domande frequenti

Cosa fa keyof in TypeScript?

keyof T produce un'unione dei nomi delle proprietà del tipo T. Per interface User { id: number; name: string }, keyof User è "id" | "name". Funziona solo sui tipi, in fase di compilazione, e non produce JavaScript.

Cos'è keyof typeof in TypeScript?

keyof richiede un tipo, ma un oggetto semplice come const colors = { red: "#f00" } è un valore. typeof colors trasforma il valore nel suo tipo, e keyof typeof colors dà poi l'unione delle sue chiavi, qui "red". Scrivere keyof colors fallisce con l'errore TS2749.

Perché keyof restituisce string | number?

Quando il tipo ha una index signature di tipo stringa come { [key: string]: number }. JavaScript converte in stringhe le chiavi numeriche delle proprietà, quindi anche obj[42] è un accesso valido, e TypeScript include number nel tipo delle chiavi. Usa Extract<keyof T, string> o string & keyof T se vuoi solo le chiavi stringa.

Perché Object.keys restituisce string[] invece di (keyof T)[]?

Perché un oggetto può contenere più proprietà di quelle elencate dal suo tipo: un valore con un campo in più può essere assegnato a un tipo più ristretto. Tipizzare il risultato come (keyof T)[] sarebbe una bugia per quegli oggetti. Se sai che l'oggetto ha esattamente le chiavi dichiarate, fai un cast: Object.keys(obj) as (keyof typeof obj)[].

Come si risolve "Element implicitly has an 'any' type because expression of type 'string' can't be used to index type"?

L'indice è una semplice string, e il tipo dell'oggetto non ha una index signature di tipo stringa. Tipizza il parametro come keyof YourType invece di string, oppure controlla prima la stringa con un type guard che la restringa a keyof YourType, oppure dai all'oggetto una index signature o un tipo Record<string, V> se accetta davvero qualsiasi chiave.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA