Introduzione a TypeScript
Pubblicato: 2020-10-29I primi linguaggi con cui ho programmato sono C e Java. Con questi linguaggi, ogni volta che definisci una variabile devi specificarne il tipo. Se provi ad assegnargli un valore di un altro tipo, il compilatore si lamenterà:
// main.c int main() { int x = "two"; return 0; } > cc main.c -o main // warning: initialization makes integer from pointer without a castIl problema è che, a causa della mia mancanza di esperienza, molte delle lamentele che ho ricevuto dal compilatore sembravano criptiche e complesse. Ecco perché, alla fine, ho considerato il compilatore come uno strumento che limitava la mia creatività, quando in realtà dovrebbe essere un partner che è lì per aiutarmi.

Più avanti nella mia carriera, ho iniziato a usare alcuni linguaggi di programmazione senza una digitazione forte, come JavaScript o PHP. Ho pensato che fossero piuttosto interessanti: è stato estremamente facile prototipare le cose rapidamente senza dover gestire un compilatore schizzinoso.
Come già saprai, i linguaggi di programmazione su cui si basa WordPress sono PHP e JavaScript. Ciò significa che probabilmente sei abituato a codificare i tuoi plugin e temi senza che un compilatore ti guardi le spalle. Sei solo tu, le tue capacità e la tua creatività. Bene, ed errori come questo:
const user = getUser( userId ); greet( user.name ); // Uncaught TypeError: user is undefined Se sei stufo di bug undefined nel tuo codice, è ora di aggiungere un compilatore al tuo flusso di lavoro. Diamo un'occhiata a cos'è TypeScript e come ci consente di migliorare la qualità del nostro software di diversi ordini di grandezza.
Cos'è TypeScript
TypeScript è un linguaggio di programmazione basato su JavaScript che è stato creato con l'obiettivo di aggiungere una tipizzazione forte e statica. I tipi TypeScript ci consentono di descrivere la forma dei nostri oggetti e variabili, risultando in un codice meglio documentato e più robusto. TypeScript stesso sarà responsabile della convalida di tutto ciò che facciamo.
Come progettato, TypeScript è un superset di JavaScript. Ciò significa che qualsiasi codice scritto in JavaScript semplice è, per definizione, anche TypeScript valido. Ma non è vero il contrario: se utilizzi funzionalità specifiche di TypeScript, il codice risultante non è JavaScript valido finché non lo traspondi.
Come funziona TypeScript
Per capire come funziona TypeScript useremo il suo Playground , un piccolo editor in cui possiamo scrivere codice TypeScript e vedere cosa ci dice il compilatore a riguardo.
Essendo un superset di JavaScript, scrivere codice TypeScript è estremamente semplice. Fondamentalmente il seguente codice JavaScript:
let user = "David"; let age = 34; let worksAtNelio = true; console.log( user, age, worksAtNelio );è anche codice TypeScript. Puoi copiarlo e incollarlo in TypeScript Playground e vedrai che viene compilato. Allora cosa c'è di così speciale? I suoi tipi, ovviamente. In JavaScript, puoi fare quanto segue:
let user = "David"; let age = 34; let worksAtNelio = true; user = { name: "Ruth" }; console.log( user, age, worksAtNelio );ma ciò attiverà un errore in TypeScript. Provalo nel parco giochi e vedrai il seguente errore:
Type '{ name: string; }' is not assignable to type 'string'.Allora di cosa si tratta?
TypeScript può dedurre automaticamente il tipo di una variabile. Ciò significa che non è necessario dirlo esplicitamente "ehi, questa variabile è una stringa" (o un numero, o un booleano, o altro); invece, esamina il valore che gli è stato assegnato per la prima volta e ne deduce il tipo in base a quello.

Nel nostro esempio, quando abbiamo definito la variabile user abbiamo assegnato la stringa di testo "David" , quindi TypeScript sa che user è (e dovrebbe sempre essere) una stringa . Il problema è che poco dopo proviamo a cambiare il tipo della nostra variabile user assegnandole un oggetto che ha una sola proprietà ( name ) il cui valore è la stringa "Ruth" . Chiaramente, questo oggetto non è una stringa , quindi TypeScript si lamenta e ci dice che l'assegnazione non può essere eseguita.
Ci sono due possibili percorsi per risolvere questo problema:
// Option 1 let user = "David"; user = "Ruth"; // OK. // Option 2 let user = { name: "David" }; user = { name: "Ruth" };Cerchiamo di essere espliciti nel definire i tipi delle nostre variabili
Ma l'inferenza del tipo è lì solo per aiutarci. Se vogliamo, possiamo dire esplicitamente a TypeScript il tipo di una variabile:
let user: string = "David"; let age: number = 34; let worksAtNelio: boolean = true; console.log( user, age, worksAtNelio );che restituisce lo stesso identico risultato dei tipi dedotti da TypeScript. Ora questo pone la domanda: se TypeScript può dedurre automaticamente i tipi, perché abbiamo bisogno di questa funzione?
Da un lato, specificare i tipi in modo esplicito può servire a documentare meglio il nostro codice (che diventerà ancora più chiaro nella prossima sezione). D'altra parte, ci consente di risolvere i limiti del sistema di inferenza di tipo. Sì, avete letto bene: ci sono casi in cui l'inferenza non è molto buona e il meglio che TypeScript può fare è dirci che “beh, non so cosa dovrebbe essere esattamente questa variabile; Immagino che possa essere qualsiasi cosa!”
Considera il seguente esempio:
const getName = ( person ) => person.name; let david = { name: "David", age: 34 }; console.log( getName( david ) ); // Parameter 'person' implicitly has an 'any' type. In questo caso, stiamo definendo una funzione getName che riceve un parametro e restituisce un valore. Nota quante cose stiamo assumendo in una funzione così semplice: ci aspettiamo un oggetto ( person ) con almeno una proprietà chiamata name . Ma non abbiamo davvero alcuna garanzia che chiunque chiami questa funzione la utilizzerà correttamente. Ma anche se lo fanno, non sappiamo ancora quale sia il tipo risultante di questa funzione. Certo, potresti presumere che un nome sarà una stringa , ma lo sai solo perché sei un essere umano e capisci il significato di quella parola. Ma guarda i seguenti esempi:
getName( "Hola" ); // Error getName( {} ); // Returns undefined getName( { name: "David" } ); // Returns a string getName( { name: true } ); // Returns a booleanogni volta che chiamiamo la funzione, otteniamo un risultato diverso! Quindi, nonostante TypeScript ci guardi le spalle, stiamo ancora riscontrando problemi con JavaScript classici: questa funzione può ricevere qualsiasi cosa e può restituire qualsiasi cosa.


La soluzione, avete indovinato, è annotare esplicitamente la funzione.
Definisci i tuoi tipi
Ma prima di farlo, vediamo un'altra funzionalità aggiuntiva di TypeScript: tipi di dati personalizzati. Finora, quasi tutti i nostri esempi hanno utilizzato tipi di base come string , number , boolean , ecc., Che penso siano abbastanza facili da capire. Abbiamo anche visto strutture di dati più complesse, come gli oggetti:
let david = { name: "David", age: 34 };ma non abbiamo prestato molta attenzione al suo tipo come dedotto da TypeScript, vero? Tutto ciò che abbiamo visto è un esempio di assegnazione non valida a causa di una mancata corrispondenza di battitura:
let user = "David"; user = { name: "Ruth" }; // Type '{ name: string; }' is not assignable to type 'string'.che in qualche modo suggeriva che il tipo dell'oggetto fosse " {name:string;} ", che puoi leggere come "questo è un oggetto con una proprietà chiamata name of type string ." Se è così che vengono definiti i tipi di oggetto, allora dovrebbe essere TypeScript valido:
let david: { name: string, age: number } = { name: "David", age: 34 };ed è davvero. Ma sono sicuro che sarai d'accordo sul fatto che è tutt'altro che conveniente. Fortunatamente, possiamo creare nuovi tipi in TypeScript.
In generale, un tipo personalizzato è fondamentalmente un tipo TypeScript normale con un nome personalizzato:
type Person = { name: string; age: number; }; let david: Person = { name: "David", age: 34 }; Splendido, no? Ora che abbiamo il tipo Persona , possiamo facilmente annotare la nostra funzione getName e specificare chiaramente il tipo del nostro parametro di input:
const getName = ( person: Person ) => person.name; E questo semplice aggiornamento fornisce a TypeScript molte informazioni! Ad esempio, ora può dedurre che il tipo di risultato di questa funzione è una stringa , perché sa per certo che anche l'attributo name di un oggetto Person è una stringa :
let david: Person = { name: "David", age: 34 }; let davidName = getName( david ); davidName = 2; // Type 'number' is not assignable to type 'string'.Ma, come sempre, puoi annotare esplicitamente il tipo di risultato se vuoi:
const getName = ( person: Person ): string => person.name;Altre cose con i tipi TypeScript...
Infine, volevo condividere con te un paio di prodezze interessanti di cui puoi trarre vantaggio se definisci i tuoi tipi in TypeScript.
TypeScript è molto impegnativo quando si tratta di assegnare valori alle variabili. Se hai definito in modo esplicito il tipo di una determinata variabile, puoi assegnare solo valori che corrispondono esattamente a quel tipo. Vediamolo con diversi esempi:
type Person = { name: string; age: number; }; let david: Person = { name: "David", age: 34 }; // OK let ruth: Person = { name: "Ruth" }; // Property 'age' is missing in type '{ name: string; }' but required in type 'Person' let toni: Person = { name: "Toni", age: 35, gender: "M" }; // Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'. Object literal may only specify known properties, and 'gender' does not exist in type 'Person'. Come puoi vedere, una variabile Persona accetta solo oggetti che soddisfano pienamente il tipo Persona . Se mancano alcuni attributi ( ruth non ha age ) o ci sono più attributi di quelli inclusi nel tipo ( toni ha un gender ), TypeScript si lamenterà e attiverà un errore di mancata corrispondenza del tipo.
Ma se stiamo parlando di invocare funzioni, le cose sono diverse! I tipi di argomento in una funzione non sono un requisito esatto, ma specificano l'interfaccia minima a cui i parametri devono essere conformi. Lo so, lo so, è troppo astratto. Diamo un'occhiata al seguente esempio, che credo renderà le cose più chiare:
type NamedObject = { name: string; }; const getName = ( obj: NamedObject ): string => obj.name; const ruth = { name: "Ruth" } getName( ruth ); // OK const david = { name: "David", age: 34 }; getName( david ); // OK const toni = { firstName: "Toni" }; getName( toni ); // Argument of type '{ firstName: string; }' is not assignable to parameter of type 'NamedObject'. Property 'name' is missing in type '{ firstName: string; }' but required in type 'NamedObject'. Come puoi vedere, abbiamo ridefinito la funzione getName come qualcosa di un po' più generico: ora prende un oggetto NamedObject , cioè un oggetto che deve avere un attributo chiamato name di tipo string . Usando questa definizione, vediamo come ruth e david corrispondano perfettamente a questi requisiti (entrambi hanno un attributo name ), ma toni no, poiché non ha l'attributo Expecte name .
Conclusione
TypeScript è un linguaggio di programmazione che estende JavaScript aggiungendo definizioni di tipi statici. Questo ci consente di essere molto più precisi nella definizione dei dati con cui lavoriamo e, soprattutto, ci aiuta a rilevare gli errori in anticipo.
Il costo dell'integrazione di TypeScript in uno stack di sviluppo è relativamente piccolo e può essere fatto gradualmente. Poiché tutto il codice JavaScript è, per definizione, TypeScript, il passaggio da JavaScript a TypeScript è automatico: puoi aggiungere tipi e abbellire il tuo codice un passaggio alla volta.
Se ti è piaciuto questo post e vuoi saperne di più, condividilo e fammi sapere nella sezione commenti qui sotto.
Immagine in primo piano di King's Church International su Unsplash.
