Menu

Punto esclamativo in TypeScript: la non-null assertion (!)

Un punto esclamativo dopo un valore, come user!, è l'operatore di non-null assertion: toglie null e undefined dal tipo senza alcun controllo a runtime. Scopri cosa fa x!, le forme di definite assignment let x!: T e prop!: T, perché sono rischiose e le alternative più sicure.

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

Un punto esclamativo dopo un'espressione, value!, è l'operatore di non-null assertion. Toglie null e undefined dal tipo, così un number | undefined si può usare come un number. È una promessa fatta al compilatore, non un controllo: a runtime non succede niente.

Senza il !, tea.toFixed(2) è l'errore TS18048, 'tea' is possibly 'undefined'. Con il !, il codice compila perché hai detto al compilatore che la chiave esiste.

Cosa fa x! a runtime: niente

Il ! viene cancellato dall'output. Il JavaScript compilato per prices.get("coffee")! è semplicemente prices.get("coffee"). Se l'asserzione è sbagliata, l'errore compare più avanti, nel primo punto in cui si usa il valore mancante:

Il programma stampa Cannot read properties of undefined (reading 'toFixed'). Il crash avviene nella riga dopo il !, e nel codice reale può essere molto più lontano: l'undefined può finire salvato in un oggetto ed esplodere in un altro file. È quella distanza a rendere rischioso il !.

Definite assignment: let x!: T

Lo stesso simbolo in una dichiarazione ha un significato affine. TypeScript tiene traccia di se una variabile viene assegnata prima di essere letta, e non riesce a seguire un'assegnazione fatta dentro un'altra funzione:

index.ts(9,13): error TS2454: Variable 'config' is used before being assigned.

let config!: { port: number }; è una definite assignment assertion: "verrà assegnata prima di qualsiasi lettura". Risolve l'errore, con lo stesso rischio di x!: se init() viene saltata, la lettura ottiene undefined. Di solito è meglio ristrutturare, per esempio const config = init(); con init che restituisce l'oggetto.

Proprietà di classe: prop!: T

Con strictPropertyInitialization (parte di strict), ogni proprietà di classe deve essere inizializzata nella sua dichiarazione o nel costruttore. Altrimenti ottieni TS2564: Property 'socket' has no initializer and is not definitely assigned in the constructor. Quando una proprietà viene impostata più tardi da un metodo o da un framework, prop!: T dice al compilatore di accettarla:

Il primo console.log mostra il buco: il tipo dice che socket c'è sempre, ma prima di open() è undefined. Chiamare c.socket.send in quel momento compila e lancia un errore. Se la proprietà può davvero mancare, dichiarala socket?: ... e controllala, oppure crea l'oggetto nel costruttore. Il caso principale in cui prop! è la pratica standard è un framework che riempie una proprietà dopo la costruzione: il @ViewChild(...) child!: ChildDirective di Angular (impostato prima che venga eseguito ngAfterViewInit), o le classi entità degli ORM, le cui colonne vengono riempite dalla libreria quando carica una riga (la documentazione di MikroORM scrive @Property() title!: string).

Alternative più sicure

La maggior parte dei ! si può sostituire con qualcosa che il compilatore controlla, o con un controllo che fallisce in modo evidente nel punto giusto:

Entrambi gli helper restringono il tipo come fa !, ma un'ipotesi sbagliata produce missing HOST sul posto invece di un TypeError da qualche altra parte. assertDefined è una assertion function (asserts value is ...): dopo la chiamata, il compilatore tratta host come una string. Altri schemi nei type guards.

Invece diScriviCosa succede quando il valore manca
user!.nameif (user) { user.name }il blocco viene saltato
user!.nameuser?.nameundefined
count!count ?? 0si usa il valore predefinito
map.get(k)!must(map.get(k), "k")un errore chiaro in quella riga
let x!: Tconst x = compute()non c'è niente che possa andare storto

Gli altri punti esclamativi

! significa cose diverse a seconda di dove si trova:

CodiceSignificato
value! (dopo un'espressione)non-null assertion, solo TypeScript
let x!: T, prop!: Tdefinite assignment assertion, solo TypeScript
!value (prima di un'espressione)NOT logico, JavaScript puro
!!valueconverte in booleano, JavaScript puro
a !== b, a != bdisuguaglianza, JavaScript puro

Solo i primi due vengono cancellati in fase di compilazione. !value e !!value vengono eseguiti a runtime e restituiscono un booleano.

Domande frequenti

Cosa significa un punto esclamativo dopo una variabile in TypeScript?

value! è l'operatore di non-null assertion. Dice al compilatore che value non è null né undefined, quindi il suo tipo perde quei due membri: string | undefined diventa string. Viene rimosso dal JavaScript generato e non aggiunge nessun controllo a runtime, quindi se ti sbagli il programma fallisce più avanti con un TypeError.

Che differenza c'è tra ! e ? in TypeScript?

x! afferma che il valore c'è e ti dà il tipo non nullo, senza controlli. x?.y controlla a runtime: se x è null o undefined si ferma e restituisce undefined. In una dichiarazione, name?: string rende opzionale una proprietà, mentre name!: string dice che una proprietà obbligatoria verrà assegnata in un punto che il compilatore non vede.

Cosa significa let x!: string?

È una definite assignment assertion. Dice al compilatore che la variabile verrà assegnata prima di essere letta, anche se il compilatore non riesce a dimostrarlo (per esempio perché l'assegnazione avviene dentro un'altra funzione). Senza, leggere la variabile è l'errore TS2454, Variable 'x' is used before being assigned.

Come risolvo "has no initializer and is not definitely assigned in the constructor"?

È l'errore TS2564 di strictPropertyInitialization. Dai alla proprietà un valore iniziale, assegnala nel costruttore, rendila opzionale (prop?: T) oppure, se un framework o un metodo di inizializzazione la imposta davvero prima dell'uso, scrivi prop!: T. Il ! è l'ultima opzione perché nessuno controlla la promessa.

L'operatore di non-null assertion è una cattiva pratica?

Non è sbagliato, ma ogni ! è un'affermazione non verificata. Configurazioni di lint come @typescript-eslint/no-non-null-assertion lo segnalano. Preferisci un controllo che restringa il tipo (if (x), x ?? fallback, x?.y) o un helper che lanci un errore chiaro. Tieni ! per i punti in cui il valore è garantito da una logica che il compilatore non riesce a seguire.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA