Tag: typescript, generics, type-system, utility-types, typing
Livello: avanzato
TL;DR: Tecniche avanzate di modellazione statica dei tipi in TypeScript: conditional types, mapped types, template literal types e pattern di narrowing sicuro per API robuste a compile-time.
Generics con vincoli (extends) garantiscono contratti precisi tra input e output.
Conditional types (T extends U ? X : Y) permettono decisioni dinamiche sui tipi a tempo di compilazione.
infer estrae variabili di tipo all'interno della clausola di controllo di un conditional type.
Mapped types iterano sulle chiavi di un tipo con la sintassi [K in keyof T].
I modificatori di omogeneità + o - possono aggiungere o rimuovere readonly e ? nei mapped types.
Key remapping con as consente di trasformare o filtrare le chiavi in un mapped type.
Template literal types combinano union di stringhe per creare formati testuali fortemente tipizzati.
Type predicate (val is Type) e assertion function (asserts val is Type) restringono i tipi nei flussi di controllo.
Discriminated union usa una proprietà comune per abilitare narrowing esaustivo e pattern matching.
Brand types simulano nominal typing su tipi strutturali primitivi per impedire assegnazioni errate accidentali.
Q: Come funziona la keyword infer nei conditional types?R: La uso dentro la condizione extends per dichiarare una variabile di tipo che TypeScript deduce automaticamente dal tipo passato, come estrarre il return type di una funzione o il tipo interno di una Promise.
Q: Qual è la differenza tra unknown e any?R: any disabilita completamente il type checking; unknown invece forza il type-narrowing prima di consentire qualsiasi operazione sul valore, rendendo il codice totalmente type-safe.
Q: Come si implementa l'exhaustive checking in uno switch su una discriminated union?R: Nel ramo default assegno il valore a una variabile di tipo never; se in futuro aggiungo una variante senza gestirla, il compilatore solleva un errore statico.
I conditional types distribuiscono automaticamente sulle union solo se il tipo controllato è un parametro nudo; per disabilitare la distribuzione si usa [T] extends [U].
L'operatore keyof su una union restituisce l'intersezione delle chiavi comuni, non l'unione di tutte le chiavi disponibili.
I type assertion con as sovrascrivono il compilatore nascondendo bug a runtime; è sempre preferibile usare type guard o schema validator.
L'operatore non-null assertion ! elimina il controllo a compile-time ma espone a crash a runtime se il valore è effettivamente null o undefined.
// Estrazione return type con infer
type CustomReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// Mapped type con key remapping e rimozione opzionalità
type ConcreteGetters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]-?: () => T[K];
};
// Nominal typing tramite branding
type Brand<K, T> = K & { readonly __brand: T };
type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;
// Exhaustive checking con never
type Action = { type: "add"; delta: number } | { type: "reset" };
function reducer(action: Action): void {
switch (action.type) {
case "add":
return;
case "reset":
return;
default: {
const _exhaustiveCheck: never = action;
return _exhaustiveCheck;
}
}
}
[ ] Regole di bivarianza e contravarianza nei parametri delle funzioni
[ ] Implementazione manuale di utility types built-in come Omit, Pick e Awaited
[ ] Manipolazione complessa di stringhe con template literal types ricorsivi
[x] Discriminated union e narrowing con switch statement