Menu

L'operatore satisfies in TypeScript: vs annotazioni e as

L'operatore satisfies controlla che un valore corrisponda a un tipo senza cambiare il tipo inferito del valore. Scopri cosa fa, come si confronta con un'annotazione di tipo e con as (lo stesso oggetto scritto in tre modi), come si combina con as const e perché è adatto agli oggetti di configurazione.

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

value satisfies Type controlla in fase di compilazione che value corrisponda a Type, e poi lascia stare il tipo proprio del valore, più preciso. Un'annotazione sostituirebbe quel tipo preciso con Type; satisfies convalida senza allargare.

satisfies fa comunque il controllo: un colore mancante, una chiave scritta male come bleu o un valore come true è un errore di compilazione su quella riga. Esiste da TypeScript 4.9 e, come ogni annotazione di tipo, viene rimosso dal JavaScript generato.

Il problema che satisfies risolve

Con un'annotazione di tipo, il tipo della variabile è l'annotazione. Il compilatore dimentica ciò che ha visto nel letterale. Qui la stessa tavolozza è annotata, e ora TypeScript non sa più che green è una stringa:

Il compilatore segnala:

index.ts(11,27): error TS2339: Property 'toUpperCase' does not exist on type 'Color'.
  Property 'toUpperCase' does not exist on type '[number, number, number]'.

Prima di TypeScript 4.9 le scelte erano: annotare e restringere a mano ovunque (typeof palette.green === "string"), oppure saltare l'annotazione e perdere il controllo. satisfies dà entrambe le cose. Cambia : Record<ColorName, Color> in satisfies Record<ColorName, Color> dopo la parentesi graffa di chiusura e funziona.

satisfies vs annotazione di tipo vs as

Lo stesso oggetto di impostazioni, scritto in tre modi:

as ha lasciato passare il lang mancante, e asserted.lang è undefined in fase di esecuzione mentre il suo tipo dice string. Togli lang dalle altre due righe ed entrambe falliscono con TS2741, Property 'lang' is missing in type ....

Annotazione const x: T = vAsserzione v as Tv satisfies T
Proprietà mancantierroreammesseerrore
Proprietà in eccesso (oggetto letterale)erroreammesseerrore
Tipo di proprietà sbagliatoerroresolo se i tipi non si sovrappongonoerrore
Tipo di x dopoTTil tipo inferito di v
Tipi letterali ("dark", 8080)allargati a Tallargati a Tmantenuti dove T li ammette
Chiavi di un Record<string, ...>qualsiasi stringa (gli errori di battitura compilano)qualsiasi stringaesattamente le chiavi scritte
Effetto in esecuzionenessunonessunonessuno

Regola pratica: annota quando vuoi che la variabile abbia il tipo dichiarato (un valore che riassegnerai, un'API pubblica), e usa satisfies quando vuoi un controllo ma il tipo proprio del valore è più utile.

Intercettare errori negli oggetti letterali

satisfies esegue il controllo di assegnabilità completo, compreso quello delle proprietà in eccesso, quindi gli errori di battitura nelle chiavi sono errori:

type Route = { path: string; method: "GET" | "POST" };

const home = { path: "/", metod: "GET" } satisfies Route;
// error TS2561: Object literal may only specify known properties, but 'metod' does not exist in type 'Route'. Did you mean to write 'method'?

Il controllo dà anche al letterale un tipo contestuale, proprio come fa un'annotazione. Questo conta in due modi. I letterali stringa vengono mantenuti come tipi letterali quando il tipo di destinazione se li aspetta: { path: "/", method: "GET" } satisfies Route ha method: "GET", mentre lo stesso oggetto senza annotazione inferirebbe method: string. E i parametri delle callback vengono inferiti dal tipo di destinazione:

Le chiavi di un Record restano note

Un uso comune è una tabella di lookup. Annotata come Record<string, T>, ogni stringa è una chiave valida e un errore di battitura compila, restituendo undefined in fase di esecuzione. Con satisfies, i valori vengono comunque controllati rispetto a T, ma il tipo della variabile elenca esattamente le chiavi che hai scritto:

keyof typeof endpoints è utile solo perché le chiavi sono sopravvissute. Con l'annotazione sarebbe un semplice string.

Per richiedere un insieme fisso di chiavi, usa satisfies con un Record su un'unione: satisfies Record<"dev" | "prod", string> segnala un prod mancante con TS2741 e uno staging sconosciuto con TS2353.

as const satisfies

as const e satisfies si combinano. Scrivi prima as const: rende il valore readonly in profondità con tipi letterali, poi satisfies controlla quel valore esatto.

Ogni route viene controllata rispetto a Route (un method: "PUT" sarebbe un errore), e la tupla di tipi letterali resta disponibile, quindi Path è un'unione dei percorsi reali. Usa readonly Route[] (o ReadonlyArray<Route>) come destinazione, dato che un array as const è readonly.

Oggetti di configurazione

La configurazione è il campo in cui satisfies si guadagna il posto: la forma deve essere giusta, e il codice altrove vuole i valori precisi.

Dimentica la voce production, scrivi male logLevel o scrivi logLevel: "verbose", e il compilatore indica la riga esatta. Lo stesso schema va bene per i file *.config.ts: export default { ... } satisfies SomeConfig controlla l'intero file mentre l'oggetto esportato mantiene i suoi valori letterali.

Quando non usare satisfies

  • La variabile verrà riassegnata. let cfg = { port: 3000 } satisfies { port: number | string } dà a cfg il tipo { port: number }, quindi un successivo cfg = { port: "80" } fallisce (TS2322). Annota le variabili che intendi modificare.
  • Vuoi di proposito il tipo dichiarato. Per il valore di ritorno di una funzione o una costante esportata che fa parte di un'API, il tipo dell'annotazione è il contratto, e far trapelare il tipo letterale esatto può rendere incompatibili le modifiche future.
  • Il valore non è un letterale. satisfies dà il meglio su oggetti e array letterali. Su una variabile o sul risultato di una chiamata è un semplice controllo di assegnabilità, che un'annotazione ti dà già.

Domande frequenti

Cosa fa satisfies in TypeScript?

expression satisfies Type controlla in fase di compilazione che l'espressione sia assegnabile a Type, segnalando proprietà mancanti, proprietà in eccesso e tipi di valore sbagliati, e poi lascia invariato il tipo inferito dell'espressione. Ottieni la sicurezza di un'annotazione e la precisione dell'inferenza. Viene cancellato dall'output JavaScript.

Che differenza c'è tra satisfies e un'annotazione di tipo?

Entrambi controllano il valore. Un'annotazione (const x: T = ...) poi dà alla variabile il tipo T, dimenticando ciò che il compilatore sapeva del valore (tipi letterali, quale membro dell'unione è ogni proprietà, quali chiavi esistono). satisfies T mantiene il tipo inferito, quindi si sa che x.someKey esiste e una proprietà string | number che contiene una stringa ha tipo string.

Che differenza c'è tra satisfies e as in TypeScript?

as è un'asserzione: sostituisce il tipo e non controlla quasi nulla, quindi le proprietà mancanti passano inosservate. satisfies è un controllo: il valore deve corrispondere davvero al tipo, e il suo tipo inferito viene mantenuto. Quando compilerebbero entrambi, satisfies è la scelta più sicura.

Cosa significa as const satisfies?

Applica entrambe le cose: as const rende il valore readonly in profondità con tipi letterali, poi satisfies controlla quel risultato rispetto a un tipo. Scrivi prima as const: const routes = [...] as const satisfies readonly Route[];. La variabile mantiene i tipi letterali esatti per usarli dopo, e una voce sbagliata resta un errore di compilazione.

Quale versione di TypeScript ha aggiunto satisfies?

TypeScript 4.9, uscito a novembre 2022. È sintassi cancellabile, quindi funziona anche con il type stripping integrato di Node, e tutte le versioni attuali di TypeScript (compresa la 7) lo supportano.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA