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.
function function forseRecuperaDatiUtente(): ["errore", Error] | ["successo", {
nome: string;
email: string;
}]
forseRecuperaDatiUtente():
| ["errore", Error]
| ["successo", { nome: stringnome: string; email: stringemail: string }] {
if (var Math: MathAn intrinsic object that provides basic mathematics functionality and constants.Math.Math.random(): numberReturns 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:
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: DateConstructorEnables basic storage and retrieval of dates and times.Date) {
let valore: Datevalore;
}
// 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: nullvalore;
}
// controllo truthy/falsy
else if (!let valore: [number] | {
intervalloDate: [Date, Date];
} | undefined
valore) {
let valore: undefinedvalore;
}
// 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: nevervalore; // 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?
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:
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: anyforseAuto; // 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
sembraUnAutoabbia qualcosa a che fare con il tipo divaloreDaTestare.
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.
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: AutoLikeforseAuto;
}Quello che vediamo qui è che TypeScript ora capisce che se
sembraUnAutorestituisce true, è sicuro assumere chevaloreDaTestaresia unAutoLike.
asserts value is Foo
C'è un altro approccio che potremmo adottare, che elimina la necessità di un condizionale.
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: anyforseAuto;
function assertSembraUnAuto(valoreDaTestare: any): asserts valoreDaTestare is AutoLikeassertSembraUnAuto(let forseAuto: anyforseAuto);
let forseAuto: AutoLikeforseAuto;
Concettualmente, quello che succede dietro le quinte è molto simile. Usando questa sintassi speciale per descrivere il tipo di ritorno, stiamo informando TypeScript che...
assertSembraUnAutolancerà un errore sevaloreDaTestareNON è tipo-equivalente adAutoLike.
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.
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: ConsoleThe `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
```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.log(const fatt: Fatturafatt.Fattura.equals(altro: any): booleanequals(const fatt: Fatturafatt)); // ✅ trueQuesto è 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:
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: Autoaltro;
return true;
}
return false;
}
}
let let val: anyval: any;
if (class AutoAuto.Auto.isAuto(altro: any): altro is AutoisAuto(let val: anyval)) {
let val: Autoval;
}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:
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:
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: ConsoleThe `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
```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.log(const zero: neverzero); // è davvero impossibile arrivare qui?
}
if (function isNullo(val: any): val is nullisNullo(const vuota: ""vuota)) {
var console: ConsoleThe `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
```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.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:
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:
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]: DataLikeferragosto; // 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:
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: Dateferragosto; // 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.
