ProgrammazioneSviluppatore Frontend/Backend

Come funziona e come opera l'opzione Strict Function Types in TypeScript? Come influisce sulla verifica dei tipi delle funzioni con covarianza e contravarianza, e in quali casi una discrepanza tra le firme porterà a un errore di compilazione?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

Storia della domanda

Per impostazione predefinita, TypeScript consente una certa "flessibilità" nel confrontare le firme di tipo delle funzioni, permettendo che le funzioni covarianti e contravarianti siano considerate compatibili. A partire da TypeScript 2.6, è stata introdotta l'opzione strictFunctionTypes, che garantisce un controllo rigido dei tipi delle funzioni e previene molte classi di errori, specialmente nelle grandi basi di codice.

Problema

Senza un controllo rigoroso, può verificarsi una situazione in cui un gestore di funzioni o un callback accetta un numero maggiore o un tipo di parametro più specifico e questo passa inosservato per lo sviluppatore. Ciò porta a errori di runtime legati alla covarianza dei tipi di ritorno e alla contravarianza degli argomenti.

Soluzione

L'opzione strictFunctionTypes introduce una contravarianza rigorosa per i tipi dei parametri delle funzioni. Ora le funzioni sono compatibili solo se il parametro di tipo sorgente è un supremo del parametro di destinazione, e non viceversa.

Esempio di codice:

type Animal = { name: string }; type Cat = { name: string; meow: () => void }; let animalHandler: (a: Animal) => void; let catHandler: (c: Cat) => void; animalHandler = catHandler; // Errore con strictFunctionTypes: argomento troppo specifico catHandler = animalHandler; // Permesso, Cat è un sottotipo di Animal

Caratteristiche chiave:

  • Gli argomenti delle funzioni vengono controllati per compatibilità "suprema" (contravarianza)
  • I valori restituiti vengono controllati per covarianza (sottotipi consentiti)
  • La violazione delle firme porta a errori di compilazione

Domande insidiose.

Era possibile assegnare un gestore con un tipo di parametro più specifico prima dell'introduzione di strictFunctionTypes?

Sì, prima dell'attivazione di strictFunctionTypes, TypeScript consentiva di assegnare funzioni più specifiche al posto di quelle generali, cosa che portava a problemi di runtime:

enum E { A, B } const f: (e: E) => void = (e: E.A) => {} // Senza strictFunctionTypes: permesso

Come influisce strictFunctionTypes sui callback con parametri opzionali?

Se i parametri di una funzione callback rendono alcuni parametri opzionali, il controllo rigoroso non consentirà di utilizzare una funzione con un numero inferiore di parametri obbligatori in una posizione in cui ci si aspetta una funzione con un numero maggiore. Questo previene situazioni in cui il callback non riceve i dati necessari.

Ci saranno problemi di compatibilità attivando strictFunctionTypes in progetti esistenti?

Sì, c'è il rischio di nuovi errori di compilazione, poiché molte funzioni e gestori potrebbero essere stati assegnati l'uno all'altro con violazione della contravarianza. Questo si verifica più spesso con chiamate inverse o con l'uso delle API di librerie di terze parti senza una rigorosa tipizzazione.

Errori tipici e anti-pattern

  • Utilizzo di tipizzazione obsoleta per i callback senza un controllo rigoroso dei parametri
  • Tentativo di assegnare una funzione con un tipo di parametro più specifico a un gestore più generale
  • Ignorare gli errori quando si attiva strictFunctionTypes (commentare l'opzione invece di correggere i tipi)

Esempio dalla vita reale

Caso negativo

In un grande progetto, i gestori di eventi accettano tipi più specifici (MouseEvent invece del generale Event). Questo non viene scoperto fino all'attivazione dell'opzione rigorosa, portando a errori durante l'esecuzione con diverse fonti di eventi.

Pro:

  • Prototipazione più rapida

Contro:

  • Bug di runtime a seguito di discrepanze tra tipi di eventi
  • Debugging complesso dopo l'espansione del codice

Caso positivo

Il progetto utilizza strictFunctionTypes fin dall'inizio. Quando si aggiungono nuovi gestori, tutte le discrepanze tra i tipi vengono automaticamente rilevate dal compilatore. Il codice diventa più resistente agli errori di battitura e più facile da mantenere.

Pro:

  • Affidabilità
  • Sicurezza nella trasmissione di funzioni e gestori
  • Comportamento prevedibile durante il refactoring

Contro:

  • Richiede una progettazione attenta delle firme
  • In alcuni casi, è necessario scrivere wrapper o overload aggiuntivi per la compatibilità