Pompei Tech
Guide · typescript

Type guard e narrowing

Type guard e narrowing

Pensa al triage in un pronto soccorso. Quando arrivi, un infermiere valuta rapidamente i tuoi sintomi — febbre, dolore al petto, una frattura evidente — e in base a quello che osserva ti indirizza verso il reparto giusto: cardiologia, ortopedia, medicina generale. Prima di quella valutazione, sei semplicemente "un paziente", una categoria troppo generica per essere curata. Dopo, sei "un paziente con sospetto infarto", e tutto il personale sa esattamente come trattarti.

I type guard fanno lo stesso lavoro dell'infermiere del triage, ma con i tipi: usati insieme al controllo di flusso, permettono agli sviluppatori TypeScript di creare rami di codice che hanno assunzioni concrete su quello che, di partenza, poteva essere un tipo piuttosto vago. Un esempio con cui abbiamo già lavorato è il concetto di union discriminata, dove abbiamo preso un valore che poteva indicare informazioni di successo o di errore, e abbiamo usato un controllo di uguaglianza con il discriminatore ("successo" | "errore") per mandare il caso di successo lungo un ramo di codice, e il caso di errore lungo un altro.

ts
function 
function forseRecuperaDatiUtente(): ["errore", Error] | ["successo", {
    nome: string;
    email: string;
}]
forseRecuperaDatiUtente
():
| ["errore", Error] | ["successo", { nome: stringnome: string; email: stringemail: string }] { if (var Math: Math
An intrinsic object that provides basic mathematics functionality and constants.
Math
.Math.random(): number
Returns a pseudorandom number between 0 and 1.
random
() > 0.5) {
return ["successo", { nome: stringnome: "Davide", email: stringemail: "davide@example.com" }]; } else { return ["errore", new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
("La moneta è uscita CROCE :(")];
} } const
const esito: ["errore", Error] | ["successo", {
    nome: string;
    email: string;
}]
esito
=
function forseRecuperaDatiUtente(): ["errore", Error] | ["successo", {
    nome: string;
    email: string;
}]
forseRecuperaDatiUtente
();
if (
const esito: ["errore", Error] | ["successo", {
    nome: string;
    email: string;
}]
esito
[0] === "errore") {
const esito: ["errore", Error]
esito
;
} else {
const esito: ["successo", {
    nome: string;
    email: string;
}]
esito
;
}

Se hai un occhio molto attento, avrai notato che abbiamo usato in modo simile anche typeof e instanceof. C'è molto di più da esplorare su questo argomento, incluso il modo di progettare type guard personalizzati.

Type guard integrati

TypeScript include una serie di type guard pronti all'uso. Qui sotto trovi un esempio illustrativo di diversi tipi:

ts
let 
let valore: Date | "ananas" | [number] | {
    intervalloDate: [Date, Date];
} | null | undefined
valore
:
| Date | null | undefined | "ananas" | [number] | { intervalloDate: [Date, Date]intervalloDate: [Date, Date] }; // instanceof if (
let valore: Date | "ananas" | [number] | {
    intervalloDate: [Date, Date];
} | null | undefined
valore
instanceof var Date: DateConstructor
Enables basic storage and retrieval of dates and times.
Date
) {
let valore: Date
valore
;
} // typeof else if (typeof
let valore: "ananas" | [number] | {
    intervalloDate: [Date, Date];
} | null | undefined
valore
=== "string") {
let valore: "ananas"
valore
;
} // controllo di valore specifico else if (
let valore: [number] | {
    intervalloDate: [Date, Date];
} | null | undefined
valore
=== null) {
let valore: null
valore
;
} // controllo truthy/falsy else if (!
let valore: [number] | {
    intervalloDate: [Date, Date];
} | undefined
valore
) {
let valore: undefined
valore
;
} // alcune funzioni integrate else if (var Array: ArrayConstructorArray.ArrayConstructor.isArray(arg: any): arg is any[]isArray(
let valore: [number] | {
    intervalloDate: [Date, Date];
}
valore
)) {
let valore: [number]
valore
;
} // controllo di presenza di proprietà else if ("intervalloDate" in
let valore: {
    intervalloDate: [Date, Date];
}
valore
) {
let valore: {
    intervalloDate: [Date, Date];
}
valore
;
} else {
let valore: never
valore
; // never: tutti i casi sono già stati coperti!
}

Type guard definiti dall'utente

Se vivessimo in un mondo dove avessimo solo i type guard che abbiamo visto finora, incontreremmo presto dei problemi man mano che il nostro uso dei type guard integrati diventa più complesso.

Per esempio, come faremmo a validare oggetti che sono tipo-equivalenti alla nostra interfaccia AutoLike qui sotto?

ts
interface AutoLike {
  AutoLike.marca: stringmarca: string;
  AutoLike.modello: stringmodello: string;
  AutoLike.anno: numberanno: number;
}

let let forseAuto: unknownforseAuto: unknown;

// il guard
if (
  let forseAuto: unknownforseAuto &&
  typeof let forseAuto: {}forseAuto === "object" &&
  "marca" in let forseAuto: objectforseAuto &&
  typeof (let forseAuto: object & Record<"marca", unknown>forseAuto as any)["marca"] === "string" &&
  "modello" in let forseAuto: object & Record<"marca", unknown>forseAuto &&
  typeof (let forseAuto: object & Record<"marca", unknown> & Record<"modello", unknown>forseAuto as any)["modello"] === "string" &&
  "anno" in let forseAuto: object & Record<"marca", unknown> & Record<"modello", unknown>forseAuto &&
  typeof (let forseAuto: object & Record<"marca", unknown> & Record<"modello", unknown> & Record<"anno", unknown>forseAuto as any)["anno"] === "number"
) {
  
let forseAuto: object & Record<"marca", unknown> & Record<"modello", unknown> & Record<"anno", unknown>
forseAuto
; // ancora "unknown" senza qualche trucco aggiuntivo
}

Validare questo tipo potrebbe essere possibile, ma coinvolgerebbe quasi certamente qualche cast. Anche se funzionasse, diventa abbastanza disordinato da volerlo rifattorizzare in una funzione o qualcosa di simile, così da poterlo riusare in tutto il codebase.

Vediamo cosa succede quando proviamo a farlo:

ts
interface AutoLike {
  AutoLike.marca: stringmarca: string;
  AutoLike.modello: stringmodello: string;
  AutoLike.anno: numberanno: number;
}

// il guard
function function sembraUnAuto(valoreDaTestare: any): anysembraUnAuto(valoreDaTestare: anyvaloreDaTestare: any) {
  return (
    valoreDaTestare: anyvaloreDaTestare &&
    typeof valoreDaTestare: anyvaloreDaTestare === "object" &&
    "marca" in valoreDaTestare: anyvaloreDaTestare &&
    typeof valoreDaTestare: anyvaloreDaTestare["marca"] === "string" &&
    "modello" in valoreDaTestare: anyvaloreDaTestare &&
    typeof valoreDaTestare: anyvaloreDaTestare["modello"] === "string" &&
    "anno" in valoreDaTestare: anyvaloreDaTestare &&
    typeof valoreDaTestare: anyvaloreDaTestare["anno"] === "number"
  );
}

let let forseAuto: anyforseAuto: any;

// usando il guard
if (function sembraUnAuto(valoreDaTestare: any): anysembraUnAuto(let forseAuto: anyforseAuto)) {
  
let forseAuto: any
forseAuto
; // l'effetto di narrowing è sparito!
}

Come puoi vedere, l'effetto di narrowing (per quanto imperfetto) di questo condizionale è scomparso.

Per come stanno le cose ora, TypeScript sembra non avere idea che il valore di ritorno di sembraUnAuto abbia qualcosa a che fare con il tipo di valoreDaTestare.

value is Foo

Il primo tipo di type guard definito dall'utente che vediamo è un guard is. È perfetto per il nostro esempio sopra, perché è pensato per lavorare in collaborazione con un'istruzione di controllo del flusso, per indicare che rami diversi del "flusso" verranno presi in base a una valutazione del tipo di valoreDaTestare.

ts
interface AutoLike {
  AutoLike.marca: stringmarca: string;
  AutoLike.modello: stringmodello: string;
  AutoLike.anno: numberanno: number;
}

// il guard
function function sembraUnAuto(valoreDaTestare: any): valoreDaTestare is AutoLikesembraUnAuto(valoreDaTestare: anyvaloreDaTestare: any): valoreDaTestare: anyvaloreDaTestare is AutoLike {
  return (
    valoreDaTestare: anyvaloreDaTestare &&
    typeof valoreDaTestare: anyvaloreDaTestare === "object" &&
    "marca" in valoreDaTestare: anyvaloreDaTestare &&
    typeof valoreDaTestare: anyvaloreDaTestare["marca"] === "string" &&
    "modello" in valoreDaTestare: anyvaloreDaTestare &&
    typeof valoreDaTestare: anyvaloreDaTestare["modello"] === "string" &&
    "anno" in valoreDaTestare: anyvaloreDaTestare &&
    typeof valoreDaTestare: anyvaloreDaTestare["anno"] === "number"
  );
}

let let forseAuto: anyforseAuto: any;

// usando il guard
if (function sembraUnAuto(valoreDaTestare: any): valoreDaTestare is AutoLikesembraUnAuto(let forseAuto: anyforseAuto)) {
  
let forseAuto: AutoLike
forseAuto
;
}

Quello che vediamo qui è che TypeScript ora capisce che se sembraUnAuto restituisce true, è sicuro assumere che valoreDaTestare sia un AutoLike.

asserts value is Foo

C'è un altro approccio che potremmo adottare, che elimina la necessità di un condizionale.

ts
interface AutoLike {
  AutoLike.marca: stringmarca: string;
  AutoLike.modello: stringmodello: string;
  AutoLike.anno: numberanno: number;
}

// il guard
function function assertSembraUnAuto(valoreDaTestare: any): asserts valoreDaTestare is AutoLikeassertSembraUnAuto(
  valoreDaTestare: anyvaloreDaTestare: any,
): asserts valoreDaTestare: anyvaloreDaTestare is AutoLike {
  if (
    !(
      valoreDaTestare: anyvaloreDaTestare &&
      typeof valoreDaTestare: anyvaloreDaTestare === "object" &&
      "marca" in valoreDaTestare: anyvaloreDaTestare &&
      typeof valoreDaTestare: anyvaloreDaTestare["marca"] === "string" &&
      "modello" in valoreDaTestare: anyvaloreDaTestare &&
      typeof valoreDaTestare: anyvaloreDaTestare["modello"] === "string" &&
      "anno" in valoreDaTestare: anyvaloreDaTestare &&
      typeof valoreDaTestare: anyvaloreDaTestare["anno"] === "number"
    )
  )
    throw new 
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
(`Il valore non sembra essere un AutoLike: ${valoreDaTestare: anyvaloreDaTestare}`);
} let let forseAuto: anyforseAuto: any; // usando il guard
let forseAuto: any
forseAuto
;
function assertSembraUnAuto(valoreDaTestare: any): asserts valoreDaTestare is AutoLikeassertSembraUnAuto(let forseAuto: anyforseAuto);
let forseAuto: AutoLike
forseAuto
;

Concettualmente, quello che succede dietro le quinte è molto simile. Usando questa sintassi speciale per descrivere il tipo di ritorno, stiamo informando TypeScript che...

assertSembraUnAuto lancerà un errore se valoreDaTestare NON è tipo-equivalente ad AutoLike.

Quindi, se superiamo l'asserzione e continuiamo a eseguire codice sulla riga successiva, il tipo cambia da any ad AutoLike.

Uso con controlli di presenza di campi #privati

Come discusso nel capitolo precedente, un metodo static o di istanza di una classe può usare un campo privato #campo per rilevare se un oggetto è un'istanza della stessa classe.

ts
class class FatturaFattura {
  static #prossimoIdFattura = 1;
  #idFattura = class FatturaFattura.#prossimoIdFattura++;

  Fattura.equals(altro: any): booleanequals(altro: anyaltro: any): boolean {
    return (
      altro: anyaltro && // è truthy
      typeof altro: anyaltro === "object" && // ed è un oggetto
      #idFattura in altro: anyaltro && // ed è "marchiato" con la proprietà #idFattura
      altro: Fatturaaltro.#idFattura === this.#idFattura // e i valori di #idFattura coincidono
    );
  }
}

const const fatt: Fatturafatt = new constructor Fattura(): FatturaFattura();
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v22.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(const fatt: Fatturafatt.Fattura.equals(altro: any): booleanequals(const fatt: Fatturafatt)); // ✅ true

Questo è piuttosto comodo dal punto di vista dei type guard. Possiamo usare questa tecnica in un metodo statico, e usare la nostra variante di guard is per creare un type guard interessante:

ts
class class AutoAuto {
  static #prossimaMatricola: number = 100;
  static #generaMatricola() {
    return this.#prossimaMatricola++;
  }

  #matricola = class AutoAuto.#generaMatricola();

  static Auto.isAuto(altro: any): altro is AutoisAuto(altro: anyaltro: any): altro: anyaltro is class AutoAuto {
    if (
      altro: anyaltro && // è truthy
      typeof altro: anyaltro === "object" && // ed è un oggetto
      #matricola in altro: anyaltro
    ) {
      // e riusciamo a trovare un campo privato a cui possiamo accedere da qui
      // allora *deve* essere un'Auto
      
altro: Auto
altro
;
return true; } return false; } } let let val: anyval: any; if (class AutoAuto.Auto.isAuto(altro: any): altro is AutoisAuto(let val: anyval)) {
let val: Auto
val
;
}

Un type guard come questo non è sempre la decisione giusta. Ricorda, TypeScript usa un sistema di tipi strutturale, e qui abbiamo effettivamente costruito qualcosa che si comporta in modo nominale. Niente, a parte un'istanza di Auto, supererà il test isAuto, perché questo metodo static può accedere solo ai campi privati su Auto. Non è una cosa negativa, e non è di certo peggio del type guard integrato instanceof.

Narrowing con switch(true)

A volte una lunga cascata di type guard in blocchi if/else può risultare un po' verbosa, specialmente se l'azione da intraprendere in ogni ramo del condizionale è solo un paio di righe di codice. TypeScript 5.3 ha introdotto la possibilità di usare switch(true) per il narrowing:

ts
class class PescePesce {
  Pesce.nuota(): voidnuota(): void {}
}
class class UccelloUccello {
  Uccello.vola(): voidvola(): void {}
}

let let val: anyval = {} as any;
switch (true) {
  case let val: anyval instanceof class UccelloUccello:
    let val: Uccelloval.Uccello.vola(): voidvola();
    break;
  case let val: anyval instanceof class PescePesce:
    let val: Pesceval.Pesce.nuota(): voidnuota();
    break;
}

Scrivere type guard di qualità

I type guard possono essere pensati come parte della "colla" che collega il type checking a compile time con l'esecuzione del tuo programma a runtime. È di grande importanza che siano progettati bene, perché TypeScript ti prenderà in parola quando affermi qualcosa sul valore di ritorno (o sul comportamento di lancio/non lancio di un'eccezione) che indicano.

Guardiamo un cattivo esempio di type guard:

ts
function function isNullo(val: any): val is nullisNullo(val: anyval: any): val: anyval is null {
  return !val: anyval;
}

const const vuota: ""vuota = "";
const const zero: 0zero = 0;
if (function isNullo(val: any): val is nullisNullo(const zero: 0zero)) {
  var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v22.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(const zero: neverzero); // è davvero impossibile arrivare qui?
} if (function isNullo(val: any): val is nullisNullo(const vuota: ""vuota)) { var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v22.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(const vuota: nevervuota); // è davvero impossibile arrivare qui?
}

Se esegui questo codice, vedrai stampati in console sia 0 che "". Errori comuni come dimenticare che stringhe e numeri possono essere falsy possono creare una falsa sicurezza sulla correttezza del tuo codice. Le "bugie" nei tuoi type guard si propagano rapidamente nel tuo codebase e causano problemi piuttosto difficili da risolvere.

Nei casi in cui il resto del tuo codice fa affidamento sul fatto che un determinato valore sia di un certo tipo, assicurati di lanciare (throw) un errore, così che un comportamento inatteso sia rumoroso invece che silenzioso.

La keyword satisfies

Guardiamo il seguente scenario:

ts
type DataLike = Date | number | string;

type Festivita = {
  [k: string]: DataLike;
};

const festivitaItaliane = {
  festaDellaRepubblica: "2 Giugno 2024",
  ferragosto: new Date("August 15, 2024"),
  santoStefano: 1735167600000, // 25 dicembre 2024
};

Come possiamo assicurarci che festivitaItaliane sia conforme al tipo Festivita? Potremmo usare un'annotazione di tipo:

ts
type type DataLike = string | number | DateDataLike = Date | number | string;

type 
type Festivita = {
    [k: string]: DataLike;
}
Festivita
= {
[k: stringk: string]: type DataLike = string | number | DateDataLike; }; const const festivitaItaliane: FestivitafestivitaItaliane:
type Festivita = {
    [k: string]: DataLike;
}
Festivita
= {
festaDellaRepubblica: stringfestaDellaRepubblica: "2 Giugno 2024", ferragosto: Dateferragosto: new
var Date: DateConstructor
new (value: number | string | Date) => Date (+4 overloads)
Date
("August 15, 2024"),
santoStefano: numbersantoStefano: 1735167600000, }; const festivitaItaliane: FestivitafestivitaItaliane.
Festivita[string]: DataLike
ferragosto
; // non più "Date" — abbiamo perso informazione!

ma abbiamo perso qualche informazione di tipo specifica (per esempio, era chiaro che ferragosto fosse un Date, ma ora dobbiamo trattarlo come Date | number | string). Otterremmo lo stesso risultato se provassimo a fare un cast usando as Festivita.

Potremmo provare a farlo passare attraverso un type guard e vedere cosa succede, ma finiremmo per eseguire codice aggiuntivo a runtime (l'implementazione del type guard) solo per ottenere il beneficio di un type check a compile time — piuttosto scomodo.

La keyword satisfies rende tutto questo molto più semplice:

ts
type type DataLike = string | number | DateDataLike = Date | number | string;

type 
type Festivita = {
    [k: string]: DataLike;
}
Festivita
= {
[k: stringk: string]: type DataLike = string | number | DateDataLike; }; const
const festivitaItaliane: {
    festaDellaRepubblica: string;
    ferragosto: Date;
    santoStefano: number;
}
festivitaItaliane
= {
festaDellaRepubblica: stringfestaDellaRepubblica: "2 Giugno 2024", ferragosto: Dateferragosto: new
var Date: DateConstructor
new (value: number | string | Date) => Date (+4 overloads)
Date
("August 15, 2024"),
santoStefano: numbersantoStefano: 1735167600000, } satisfies
type Festivita = {
    [k: string]: DataLike;
}
Festivita
;
const festivitaItaliane: {
    festaDellaRepubblica: string;
    ferragosto: Date;
    santoStefano: number;
}
festivitaItaliane
.
ferragosto: Date
ferragosto
; // abbiamo mantenuto l'informazione specifica!

È importante ricordare che qui non stiamo davvero eseguendo un type guard — l'operatore satisfies usa esclusivamente le informazioni di tipo, basate su ciò che è stato inferito dalla dichiarazione di festivitaItaliane e su ciò che è stato dichiarato per il tipo Festivita, senza generare alcun codice aggiuntivo a runtime.