Vai al contenuto
Web

TypeScript strict mode: i 9 setting che non disattiviamo mai

strict: true accende 9 controlli e da TypeScript 6.0 è il default. Cosa intercetta ogni flag in produzione, più i 3 flag fuori dal bundle che aggiungiamo.

25 maggio 2026 · 10 min di lettura · Aggiornato il 7 ottobre 2026

On an amaranth ground, nine dark concentric rings around a white core, nine dark lines arriving from outside, each stopped at a different ring

TypeScript strict mode è un singolo flag dentro tsconfig.json che attiva nove controlli separati del compilatore tutti insieme. I nove sono strictNullChecks, noImplicitAny, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables, alwaysStrict e strictBuiltinIteratorReturn, entrato nella famiglia con TypeScript 5.6. La reference TSConfig di Microsoft li elenca dentro la famiglia strict e da TypeScript 6.0, uscito nel marzo 2026, strict è attivo di default: la issue 62333 è stata chiusa sulla milestone 6.0.0, quindi oggi un progetto deve spegnere strict di proposito per tornare al comportamento permissivo.

La maggior parte dei codebase parte con strict: true alla radice e una manciata di file o moduli che in silenzio rinunciano a uno o due di questi controlli. Le rinunce quasi mai tornano indietro. Questo articolo è l'elenco dei nove, con il fallimento che ognuno previene e il prezzo che abbiamo pagato per tenerli accesi. Nessuno è gratis. Nessuno ci è mai costato più del bug che ha intercettato.

Perché il bundle conta

Il flag strict è l'unico contratto stabile. Se un team disattiva uno dei nove, ogni lettore del codice deve ora ricordare quale è stato spento. Ogni dipendenza tipizzata contro strict acquisisce un buco in più. Ogni sviluppatore junior che entra nel team riscopre lo stesso inghippo. Il bundle non è una checklist; è una dichiarazione pubblica su come un codebase ragiona sui tipi. Una volta che un flag è spento, gli altri seguono lentamente. Da 6.0 quella dichiarazione si fa per omissione: strict è il default, quindi ogni rinuncia è ormai una riga scritta di proposito nella configurazione.

I 9 controlli dentro strict

1. strictNullChecks

Cosa intercetta. null e undefined come tipi distinti da ogni altro tipo. A flag spento, una funzione che ritorna User ritorna implicitamente User | null | undefined, e ogni dereferenza è insicura.

Il costo. Ogni valore da una risposta API, ogni prop opzionale, ogni return di Array.prototype.find() richiede un narrowing.

Perché lo lasciamo acceso. Ogni codebase che abbiamo ereditato con strictNullChecks: false spedisce un bug Cannot read properties of undefined in produzione almeno una volta al trimestre. Il costo runtime di uno di questi crash supera il costo dev di un if di parecchi ordini di grandezza. È la feature di sistema dei tipi più importante in TypeScript, e il consiglio di migrazione della community è costante: attivalo per ultimo perché produce il maggior numero di errori, e non spegnerlo mai più.

2. noImplicitAny

Cosa intercetta. Parametri e valori di ritorno senza annotazione e non inferibili. A flag spento, quelle posizioni diventano in silenzio any.

Il costo. Annotazioni in più sugli helper veloci, soprattutto durante la migrazione da JavaScript.

Perché lo lasciamo acceso. any è un virus. Un solo any in un helper profondo cancella i tipi su tutti i chiamanti, spesso senza un avviso. Ogni team che ho visto disattivare noImplicitAny "per la migrazione" un anno dopo aveva la maggior parte del codice nuovo non tipizzato. Il flag è l'unico meccanismo che obbliga il team a una scelta cosciente quando un tipo è sconosciuto, invece di scivolare in any per inerzia.

3. strictFunctionTypes

Cosa intercetta. Violazioni di controvarianza sui parametri di funzione. Una funzione che riceve un Dog non può essere assegnata a una variabile che si aspetta una funzione che riceve un qualsiasi Animal, perché il tipo più largo la chiamerebbe con valori che la funzione più stretta non sa gestire.

Il costo. Attrito occasionale con tipi di libreria scritti prima dell'esistenza di questo flag. Il flag inoltre non si applica ai parametri in sintassi metodo, quindi i casi più rumorosi restano silenziosi.

Perché lo lasciamo acceso. I bug di callback sono silenziosi a runtime. Un handler che si aspetta il sottotipo sbagliato viene chiamato con valori che non sa gestire e o crasha in profondità nel render o esegue in silenzio la cosa sbagliata. Nel nostro codice non abbiamo mai avuto un errore strictFunctionTypes che si rivelasse un falso positivo.

4. strictBindCallApply

Cosa intercetta. Argomenti sbagliati passati a .bind(), .call() o .apply().

Il costo. Praticamente zero nei codebase moderni, perché .bind() nessuno lo scrive più.

Perché lo lasciamo acceso. Il flag è gratis nel codice che usa arrow function, metodi di classe e hook React. Spegnerlo conterebbe solo per codice legacy che andrebbe modernizzato comunque. Non c'è alcun vantaggio nel disattivarlo.

5. strictPropertyInitialization

Cosa intercetta. Campi di classe dichiarati con un tipo non-undefined ma mai assegnati nel costruttore. Senza questo controllo, il campo è in silenzio undefined alla prima lettura.

Il costo. I campi vanno inizializzati nel costruttore, marcati con l'operatore di assegnamento definito (!), oppure il tipo deve includere undefined.

Perché lo lasciamo acceso. L'alternativa sono campi che sembrano inizializzati nel tipo ma non lo sono a runtime, e che spuntano come bug nel setup dei test e nei container DI. L'operatore ! è la via di fuga per i casi in cui un framework garantisce l'inizializzazione, e l'obbligo di scriverlo rende visibile l'assunzione. Il controllo si ripaga in ogni progetto che usa controller a classe, entità ORM o oggetti di servizio.

6. noImplicitThis

Cosa intercetta. Espressioni this il cui tipo il compilatore non riesce a inferire, tipicamente nei callback passati a forEach o in funzioni stand-alone usate come event handler.

Il costo. Zero nel codice React e Node moderno che usa arrow function e classi ES. Reale nel codice legacy class-heavy, che è poi proprio dove intercetta più bug.

Perché lo lasciamo acceso. Un this legato male è una delle famiglie di bug più antiche di JavaScript. Se TypeScript la intercetta gratis, non c'è motivo di rinunciare.

7. useUnknownInCatchVariables

Cosa intercetta. L'err dentro un blocco catch (err). Con questo flag err è tipizzato come unknown invece che any, quindi non lo si può dereferenziare senza prima fare narrowing.

Il costo. Una riga if (err instanceof Error) per blocco catch.

Perché lo lasciamo acceso. any dentro un catch è uno dei modi più comuni in cui any si infiltra nel resto del codice. err.message finisce in una stringa UI, poi in un payload di log, poi in un breadcrumb Sentry, e a quel punto any si è diffuso su quattro file. Fare narrowing una volta al confine del catch tiene onesta tutta la superficie a valle.

8. alwaysStrict

Cosa intercetta. Garantisce che ogni file parsato sia in strict mode e emette "use strict" nel JavaScript generato.

Il costo. Zero in qualsiasi codice scritto in questo decennio.

Perché lo lasciamo acceso. È già acceso di default in qualsiasi codice a forma di modulo (ESM, moduli CommonJS, classi). Il flag conta soprattutto per gli script e per i blocchi inline. Spento è un odore che qualcuno sta spedendo script semplici, che è un problema a parte.

9. strictBuiltinIteratorReturn

Cosa intercetta. Il value che leggi dal next() di un iteratore built-in. TypeScript 5.6 ha introdotto il tipo intrinseco BuiltinIteratorReturn, che è any di default e undefined con questo flag: così const { value, done } = iter.next() su un MapIterator, SetIterator o ArrayIterator tipizza value come possibilmente undefined finché non controlli done (release notes di TypeScript 5.6).

Il costo. Un controllo su done in ogni ciclo scritto a mano su map.entries() o set.values(). Zero nel for...of, che il risultato finale lo gestisce già.

Perché lo lasciamo acceso. È la stessa famiglia di bug di strictNullChecks, nell'unico punto dove strictNullChecks non arrivava. Il codice che chiama next() direttamente e legge value senza controllare done riceve undefined alla fine dell'iterazione, e con il flag spento il tipo diceva any: nessuno protestava.

Tre flag fuori da strict che attiviamo lo stesso

Il bundle strict copre il pavimento. Tre flag stanno fuori e hanno un impatto reale sul conteggio bug in produzione. Li attiviamo dal giorno uno su ogni progetto nuovo.

noUncheckedIndexedAccess

Obbliga obj[key] e arr[i] a essere tipizzati come T | undefined. Il comportamento di default assume che l'accesso vada sempre a buon fine, cosa che è falsa per qualsiasi record o array la cui forma è dinamica. La doc ufficiale lo dichiara in la pagina noUncheckedIndexedAccess, Resta fuori dal bundle strict, ma tsc --init lo scrive ormai in una configurazione nuova insieme a exactOptionalPropertyTypes, quindi un progetto appena creato li ha entrambi senza chiederlo (TypeScript PR 61813). Dal nostro lavoro: è il flag che aggiunge la pressione di type-check più alta di ogni altro singolo setting, ed elimina la classe di crash runtime più frequente che vediamo ancora nel codice con solo strict: true.

exactOptionalPropertyTypes

Separa "la proprietà potrebbe non essere impostata" da "la proprietà è impostata a undefined". La maggior parte dei team le tratta come la stessa cosa. Non lo sono, soprattutto con prop React passate attraverso wrapper e con stato di form dove un campo svuotato dall'utente produce una stringa vuota mentre un campo mai toccato produce una chiave mancante. Il flag impone la distinzione a livello di tipi e previene la fusione silenziosa che rompe la persistenza dei form.

noPropertyAccessFromIndexSignature

Obbliga obj["key"] per l'accesso dinamico e obj.key per le proprietà dichiarate. L'attrito è reale, ma il flag previene il fall-through silenzioso in cui un refuso in obj.foo ritorna undefined perché il tipo ha un index signature e il refuso non corrisponde a nessuna proprietà dichiarata.

Come migriamo un codebase legacy a strict

La migrazione ha sempre la stessa forma. Si accende strict alla radice in un branch. Si guarda il numero di errori. Si aggiunge un // @ts-nocheck in cima a ogni file che esplode, si registra il conteggio, e si rimuovono quei commenti un file per pull request. Non si abbassa strict per tenere verde la CI; gli errori soppressi oggi sono gli errori che un collega debugga alle 2 di notte il mese prossimo. Se la cremagliera si blocca, si esegue tsc --noEmit -p tsconfig.strict.json solo sui file modificati, così il codice nuovo paga il prezzo pieno mentre il codice vecchio viene ancora ripulito.

L'eccezione che abbiamo fatto, una volta, è stata su strictPropertyInitialization in un codebase NestJS dove il container DI garantisce l'inizializzazione dei campi dopo la costruzione. Anche lì, la risposta giusta è stata l'operatore ! sui campi specifici, non disattivare il flag su tutto il progetto.

Il riassunto in una riga

Ogni flag del bundle strict previene una classe di bug che abbiamo visto in produzione più di una volta. Tre flag fuori dal bundle prevengono i bug più comuni che sopravvivono a strict: true. Nessuno dei dodici ci è mai costato più del bug che ha intercettato. Se un team vuole disattivarne uno, la domanda giusta è quale bug preferisce spedire.

Aggiornato 2026-09-13: la famiglia strict è di nove flag, non otto, da quando strictBuiltinIteratorReturn è entrato con TypeScript 5.6, e strict è attivo di default da TypeScript 6.0, marzo 2026.

Domande frequenti

Articoli correlati

Studio

Inizia un progetto.

Scriviamo riguardo a ciò che costruiamo. Raccontaci cosa vuoi costruire tu.