JavaScript è tipizzato dinamicamente, il che è genuinamente utile — puoi iterare velocemente, prototipare
liberamente e pubblicare senza un passaggio di build. Ma quella flessibilità ha un costo: un'intera classe di errori
emerge solo a runtime. Una funzione si aspetta una stringa, riceve undefined, e la tua app esplode
in produzione alle 2 di notte. TypeScript
aggiunge un sistema di tipi statici su JavaScript che cattura esattamente quegli errori in fase di compilazione, nel tuo
editor, prima che qualcosa venga pubblicato. Questo articolo spiega cosa significa in pratica — con codice reale,
compromessi onesti e senza hype.
Cos'è Davvero TypeScript
TypeScript è un superset di JavaScript: ogni file .js valido è anche un file
.ts valido. Puoi rinominare un file, aggiungere zero annotazioni di tipo e compila bene. Ciò che TypeScript
aggiunge — annotazioni di tipo, interfacce, generici, enum — viene completamente rimosso in fase di compilazione. L'output
è JavaScript puro. TypeScript non cambia ciò che viene eseguito; cambia ciò che puoi sapere
prima che venga eseguito.
Il compilatore è tsc, installato tramite
npm. Lo punti ai tuoi file
.ts e genera i file .js che il tuo runtime (Node.js, un browser, Deno)
può eseguire. Nessuna sintassi specifica di TypeScript arriva mai in produzione — i tipi esistono solo per lo
sviluppatore e la toolchain. Il
repository TypeScript su GitHub
è esso stesso un grande progetto TypeScript, il che ti dà un'idea di come si scala.
# Compile a single file
npx tsc src/index.ts
# Compile a whole project (uses tsconfig.json)
npx tsc
# Watch mode — recompile on every save
npx tsc --watchIl Vantaggio Principale: Errori Prima del Runtime
Ecco il problema che TypeScript risolve più visibilmente. Immagina di lavorare con la risposta di un'API
dove un campo potrebbe essere una stringa o null. In JavaScript puro, questo fallisce silenziosamente fino alla
produzione:
// JavaScript — no error until this actually runs with a null value
function formatDisplayName(user) {
return user.displayName.toUpperCase(); // 💥 TypeError if displayName is null
}
// This looks fine in isolation. The bug only appears when a user
// has no display name set — which might be rare, but it will happen.
const name = formatDisplayName({ displayName: null });TypeScript cattura questo prima che il codice venga eseguito:
interface User {
id: number;
displayName: string | null;
email: string;
}
function formatDisplayName(user: User): string {
return user.displayName.toUpperCase();
// ❌ TypeScript error: Object is possibly 'null'.
// Property 'toUpperCase' does not exist on type 'null'.
}
// Fix: handle the null case explicitly
function formatDisplayName(user: User): string {
if (user.displayName === null) {
return user.email; // fall back to email
}
return user.displayName.toUpperCase(); // TypeScript now knows this is safe
}Questo è il momento di rivelazione per la maggior parte degli sviluppatori. Il messaggio di errore ti dice esattamente cosa c'è di sbagliato
e dove — nel tuo editor, prima di eseguire una singola riga. Con la modalità strict abilitata, TypeScript
è particolarmente aggressivo nel catturare i problemi di null e undefined attraverso la sua
funzionalità strictNullChecks,
che è attiva per impostazione predefinita in modalità strict.
tsc significa che il tuo codice ha un'inconsistenza di tipo — TypeScript può provare che
fallirà (o potrebbe fallire) senza eseguirlo. Correggi l'errore di tipo e hai eliminato completamente quella classe di bug.Il Sistema di Tipi di TypeScript in Breve
Non devi annotare tutto. TypeScript inferisce i tipi da assegnazioni, valori di ritorno e chiamate di funzione — spesso scrivi JavaScript normale e ottieni il controllo dei tipi gratuitamente. Ma conoscere la sintassi delle annotazioni aiuta quando l'inferenza non è sufficiente.
// Primitives
const productName: string = 'Wireless Keyboard';
const price: number = 79.99;
const inStock: boolean = true;
// TypeScript infers these without annotations — same effect
const productName = 'Wireless Keyboard'; // inferred: string
const price = 79.99; // inferred: number
// Arrays
const tags: string[] = ['electronics', 'peripherals'];
const ratings: number[] = [4.5, 4.8, 4.2];
// Objects with interfaces
interface Product {
id: number;
name: string;
price: number;
category: string;
inStock: boolean;
description: string | null; // union type — string or null
}
// Union types — a value that could be one of several types
type Status = 'active' | 'inactive' | 'pending'; // string literal union
type ID = string | number; // string or number
// Function with typed parameters and return type
function formatProductCard(product: Product): string {
const stockLabel = product.inStock ? 'In Stock' : 'Out of Stock';
const desc = product.description ?? 'No description available';
return `${product.name} — $${product.price.toFixed(2)} (${stockLabel})
${desc}`;
}
// Generic function — works with any type, preserves it
function firstOrDefault<T>(items: T[], fallback: T): T {
return items.length > 0 ? items[0] : fallback;
}
const first = firstOrDefault(['a', 'b', 'c'], 'z'); // inferred: string
const num = firstOrDefault([1, 2, 3], 0); // inferred: numberIl manuale di TypeScript approfondisce il sistema dei tipi — è davvero ben scritto e vale la pena leggerlo una volta acquisite le basi.
Configurare TypeScript in un Progetto
Aggiungere TypeScript a un progetto richiede circa cinque minuti. Installi il compilatore come dipendenza di sviluppo,
generi un file di configurazione e inizi a rinominare i file .js in .ts al ritmo che ha senso.
# Install TypeScript as a dev dependency
npm install -D typescript
# Generate tsconfig.json with sensible defaults
npx tsc --initIl tsconfig.json generato ha decine di opzioni, la maggior parte commentate. Le principali
da conoscere:
{
"compilerOptions": {
"target": "ES2020", // what JavaScript version to output
"module": "commonjs", // module system (commonjs for Node, ESNext for bundlers)
"outDir": "./dist", // where compiled .js files go
"rootDir": "./src", // where your .ts source files live
"strict": true, // enables all strict type checks — highly recommended
"esModuleInterop": true, // smoother interop with CommonJS modules
"skipLibCheck": true // skip type checks on .d.ts files in node_modules
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}Abilita sempre strict: true su un nuovo progetto. Raggruppa diversi
controlli tra cui strictNullChecks, noImplicitAny e
strictFunctionTypes. Su una codebase esistente potresti dover abilitarli gradualmente —
ma per qualsiasi cosa greenfield, inizia strict.
TypeScript vs JavaScript — I Veri Compromessi
TypeScript ha molti vantaggi genuini, ma anche costi reali. Ecco una valutazione onesta:
- Autocompletamento che funziona davvero. Il tuo editor conosce la forma di ogni oggetto, quindi può suggerire i nomi di proprietà e le firme dei metodi corretti invece di indovinare. Questo da solo fa risparmiare tempo ogni singolo giorno.
- Bug catturati prima che raggiungano la produzione. Dereferenziazioni null, tipi di argomento errati, proprietà richieste mancanti — TypeScript le evidenzia al momento della scrittura, non alle 3 di notte in un canale di incidente.
- Refactoring più sicuro. Rinomina un campo su un'interfaccia e TypeScript segnala immediatamente ogni sito di chiamata che deve essere aggiornato. In una grande codebase JavaScript, quel tipo di cambiamento è terrificante.
- Il codice si autodocumenta. Una firma di funzione come
sendEmail(to: string, subject: string, body: string, options?: EmailOptions): Promise<SendResult>ti dice tutto ciò che devi sapere senza leggere l'implementazione. - Migliore per i team. Quando più sviluppatori condividono una codebase, i tipi formano un contratto tra i moduli. Puoi fare il refactoring del tuo codice senza rompere quello del tuo collega.
E i costi onesti:
- Aggiunge un passaggio di build. Per un piccolo script o un prototipo rapido, eseguire
tscprima di poter testare è attrito reale. JavaScript vanilla viene eseguito immediatamente. - La configurazione iniziale richiede tempo. Configurare
tsconfig.json, aggiungere definizioni di tipo per librerie di terze parti (@types/express, ecc.) e configurare correttamente il tuo editor richiede alcune ore di lavoro. anymina tutto. TypeScript ha una via d'uscita — puoi tipizzare qualsiasi cosa comeany, il che disabilita il controllo dei tipi per quel valore. L'uso eccessivo dianysignifica che ottieni l'attrito di TypeScript senza la sicurezza. È una stampella, non una soluzione.- I tipi di librerie di terze parti possono essere in ritardo. Non tutti i pacchetti npm includono definizioni TypeScript.
Alcuni si affidano a pacchetti
@types/*mantenuti dalla community che potrebbero essere incompleti o non aggiornati.
Dove TypeScript Brilla Davvero
TypeScript paga i dividendi maggiori in tre situazioni: codebase grandi, librerie condivise e qualsiasi cosa che verrà rielaborata nel tempo. Il sistema dei tipi funge da documentazione vivente che è sempre aggiornata — a differenza di commenti o file README.
Un esempio pratico: la tua app chiama un'API REST che restituisce dati utente. Senza TypeScript, stai fidandoti che l'API restituisca quello che ti aspetti. Con TypeScript, modelli la risposta e ottieni feedback immediato se qualcosa non corrisponde:
interface ApiUser {
id: number;
username: string;
email: string;
avatarUrl: string | null;
createdAt: string; // ISO 8601 date string
role: 'admin' | 'editor' | 'viewer';
}
interface ApiResponse<T> {
data: T;
total: number;
page: number;
perPage: number;
}
async function fetchUsers(page: number): Promise<ApiResponse<ApiUser[]>> {
const response = await fetch(`/api/users?page=${page}`);
if (!response.ok) {
throw new Error(`Failed to fetch users: ${response.status}`);
}
return response.json() as Promise<ApiResponse<ApiUser[]>>;
}
// Now every consumer of this function knows exactly what they'll get
const result = await fetchUsers(1);
result.data.forEach(user => {
// TypeScript knows user.role is 'admin' | 'editor' | 'viewer'
// It will error if you try to access a property that doesn't exist
console.log(`${user.username} (${user.role})`);
});Quando hai bisogno di disattivare temporaneamente il controllo dei tipi, usa unknown invece di
any. La differenza: any ignora silenziosamente tutti i controlli, mentre unknown
ti costringe a restringere il tipo prima di usare il valore — ottieni la via d'uscita senza perdere completamente la sicurezza.
// ❌ any — TypeScript trusts you completely, no checks
function processData(data: any) {
data.nonExistentMethod(); // no error — TypeScript looks away
}
// ✅ unknown — you have to prove what it is before using it
function processData(data: unknown) {
if (typeof data === 'string') {
console.log(data.toUpperCase()); // safe — TypeScript knows it's a string here
} else if (Array.isArray(data)) {
console.log(data.length); // safe — TypeScript knows it's an array
}
}Il TypeScript Playground è il modo più veloce per sperimentare questi pattern. Incolla il codice, guarda l'output JavaScript compilato e gli eventuali errori di tipo in tempo reale — senza installazione necessaria.
Conclusione
TypeScript non è magia e non è adatto a ogni progetto. Ma per qualsiasi codebase JavaScript che stia crescendo, che venga mantenuta da più di una persona, o che verrà rielaborata — cambia genuinamente l'esperienza di sviluppo. Il sistema dei tipi cattura un'intera categoria di bug a runtime prima che vengano pubblicati, rende l'autocompletamento davvero utile e trasforma il refactoring da un processo manuale rischioso a qualcosa che la toolchain gestisce.
Inizia dalla documentazione ufficiale di TypeScript — è ben strutturata e adatta ai principianti. Il TypeScript Playground è ottimo per sperimentare senza nessuna configurazione. La voce del glossario TypeScript di MDN è una buona panoramica in una pagina se vuoi una seconda prospettiva. E se stai lavorando con dati JSON in TypeScript, lo strumento JSON to TypeScript su questo sito genera interfacce TypeScript direttamente da qualsiasi payload JSON — utile quando hai bisogno di modellare una risposta API rapidamente senza scrivere i tipi a mano.