Digitazione statica dinamica in TypeScript

Pubblicato: 2022-03-10
Riepilogo rapido ↬ In questo articolo, esamineremo alcune delle funzionalità più avanzate di TypeScript, come tipi di unione, tipi condizionali, tipi letterali modello e generici. Vogliamo formalizzare il comportamento JavaScript più dinamico in modo da poter intercettare la maggior parte dei bug prima che si verifichino. Applichiamo diversi insegnamenti da tutti i capitoli di TypeScript in 50 lezioni, un libro che abbiamo pubblicato qui su Smashing Magazine alla fine del 2020. Se sei interessato a saperne di più, assicurati di dare un'occhiata!

JavaScript è un linguaggio di programmazione intrinsecamente dinamico. Noi sviluppatori possiamo esprimere molto con poco sforzo e il linguaggio e il suo runtime capiscono cosa intendevamo fare. Questo è ciò che rende JavaScript così popolare per i principianti e che rende produttivi gli sviluppatori esperti! C'è un avvertimento, però: dobbiamo essere vigili! Errori, errori di battitura, comportamento corretto del programma: molte di queste cose accadono nelle nostre teste!

Dai un'occhiata al seguente esempio.

 app.get("/api/users/:userID", function(req, res) { if (req.method === "POST") { res.status(20).send({ message: "Got you, user " + req.params.userId }); } })

Abbiamo un server https://expressjs.com/-style che ci consente di definire un percorso (o percorso) ed esegue una richiamata se viene richiesto l'URL.

Il callback accetta due argomenti:

  1. L'oggetto della request .
    Qui otteniamo informazioni sul metodo HTTP utilizzato (ad esempio GET, POST, PUT, DELETE) e parametri aggiuntivi che entrano. In questo esempio userID dovrebbe essere mappato su un parametro userID che, beh, contiene l'ID utente!
  2. La response o l'oggetto reply .
    Qui vogliamo preparare una risposta adeguata dal server al client. Vogliamo inviare codici di stato corretti ( status del metodo) e inviare l'output JSON via cavo.

Quello che vediamo in questo esempio è molto semplificato, ma dà una buona idea di cosa stiamo facendo. Anche l'esempio sopra è pieno di errori! Dare un'occhiata:

 app.get("/api/users/:userID", function(req, res) { if (req.method === "POST") { /* Error 1 */ res.status(20).send({ /* Error 2 */ message: "Welcome, user " + req.params.userId /* Error 3 */ }); } })

Oh wow! Tre righe di codice di implementazione e tre errori? Cos'è successo?

  1. Il primo errore è sfumato. Mentre diciamo alla nostra app che vogliamo ascoltare le richieste GET (da cui app.get ), facciamo qualcosa solo se il metodo di richiesta è POST . A questo punto particolare della nostra applicazione, req.method non può essere POST . Quindi non invieremo mai alcuna risposta, il che potrebbe portare a timeout imprevisti.
  2. Ottimo che inviamo esplicitamente un codice di stato! 20 non è un codice di stato valido, però. I clienti potrebbero non capire cosa sta succedendo qui.
  3. Questa è la risposta che vogliamo restituire. Accediamo agli argomenti analizzati ma abbiamo un errore di battitura medio. È userID non userId . Tutti i nostri utenti verrebbero accolti con "Benvenuto, utente non definito!". Qualcosa che hai sicuramente visto in natura!

E cose del genere accadono! Soprattutto in JavaScript. Otteniamo espressività – non una volta abbiamo dovuto preoccuparci dei tipi – ma dobbiamo prestare molta attenzione a ciò che stiamo facendo.

Questo è anche il punto in cui JavaScript riceve molti contraccolpi dai programmatori che non sono abituati ai linguaggi di programmazione dinamici. Di solito hanno compilatori che li indirizzano a possibili problemi e rilevano gli errori in anticipo. Potrebbero sembrare altezzosi quando disapprovano la quantità di lavoro extra che devi fare nella tua testa per assicurarti che tutto funzioni bene. Potrebbero anche dirti che JavaScript non ha tipi. Il che non è vero.

Anders Hejlsberg, l'architetto principale di TypeScript, ha affermato nel suo keynote di MS Build 2017 che " non è che JavaScript non abbia un sistema di tipi. Non c'è proprio modo di formalizzarlo ”.

E questo è lo scopo principale di TypeScript. TypeScript vuole capire il tuo codice JavaScript meglio di te. E dove TypeScript non riesce a capire cosa intendi, puoi aiutare fornendo informazioni sul tipo extra.

Altro dopo il salto! Continua a leggere sotto ↓

Digitazione di base

Ed è quello che faremo adesso. Prendiamo il metodo get dal nostro server in stile Express e aggiungiamo informazioni sul tipo sufficienti in modo da poter escludere quante più categorie di errori possibili.

Iniziamo con alcune informazioni di base sul tipo. Abbiamo un oggetto app che punta a una funzione get . La funzione get accetta path , che è una stringa, e un callback.

 const app = { get, /* post, put, delete, ... to come! */ }; function get(path: string, callback: CallbackFn) { // to be implemented --> not important right now }

Mentre string è un tipo primitivo di base, CallbackFn è un tipo composto che dobbiamo definire esplicitamente.

CallbackFn è un tipo di funzione che accetta due argomenti:

  • req , che è di tipo ServerRequest
  • reply che è di tipo ServerReply

CallbackFn restituisce void .

 type CallbackFn = (req: ServerRequest, reply: ServerReply) => void;

ServerRequest è un oggetto piuttosto complesso nella maggior parte dei framework. Facciamo una versione semplificata a scopo dimostrativo. Passiamo una stringa di method , per "GET" , "POST" , "PUT" , "DELETE" , ecc. Ha anche un record di params . I record sono oggetti che associano un insieme di chiavi a un insieme di proprietà. Per ora, vogliamo consentire che ogni chiave string venga mappata su una proprietà string . Rifattorizziamo questo in seguito.

 type ServerRequest = { method: string; params: Record<string, string>; };

Per ServerReply , disponiamo alcune funzioni, sapendo che un vero oggetto ServerReply ha molto di più. Una funzione di send accetta un argomento facoltativo con i dati che vogliamo inviare. E abbiamo la possibilità di impostare un codice di stato con la funzione di status .

 type ServerReply = { send: (obj?: any) => void; status: (statusCode: number) => ServerReply; };

Questo è già qualcosa e possiamo escludere un paio di errori:

 app.get("/api/users/:userID", function(req, res) { if(req.method === 2) { // ^^^^^^^^^^^^^^^^^ Error, type number is not assignable to string res.status("200").send() // ^^^^^ Error, type string is not assignable to number } })

Ma possiamo ancora inviare codici di stato errati (qualsiasi numero è possibile) e non abbiamo idea dei possibili metodi HTTP (qualsiasi stringa è possibile). Perfezioniamo le nostre tipologie.

Set più piccoli

Puoi vedere i tipi primitivi come un insieme di tutti i possibili valori di quella determinata categoria. Ad esempio, string include tutte le possibili stringhe che possono essere espresse in JavaScript, number include tutti i numeri possibili con precisione double float. boolean include tutti i possibili valori booleani, che sono true e false .

TypeScript ti consente di perfezionare quegli insiemi in sottoinsiemi più piccoli. Ad esempio, possiamo creare un tipo Method che includa tutte le possibili stringhe che possiamo ricevere per i metodi HTTP:

 type Methods= "GET" | "POST" | "PUT" | "DELETE"; type ServerRequest = { method: Methods; params: Record<string, string>; };

Il Method è un insieme più piccolo dell'insieme di string più grande. Method è un tipo di unione di tipi letterali. Un tipo letterale è l'unità più piccola di un dato insieme. Una stringa letterale. Un numero letterale. Non c'è ambiguità. È solo "GET" . Li metti in un'unione con altri tipi letterali, creando un sottoinsieme di qualsiasi tipo più grande tu abbia. Puoi anche creare un sottoinsieme con tipi letterali di string e number o diversi tipi di oggetti composti. Ci sono molte possibilità per combinare e mettere i tipi letterali in unioni.

Ciò ha un effetto immediato sulla richiamata del nostro server. Improvvisamente, possiamo distinguere tra questi quattro metodi (o più se necessario) e possiamo esaurire tutte le possibilità nel codice. TypeScript ci guiderà:

 app.get("/api/users/:userID", function (req, res) { // at this point, TypeScript knows that req.method // can take one of four possible values switch (req.method) { case "GET": break; case "POST": break; case "DELETE": break; case "PUT": break; default: // here, req.method is never req.method; } });

Con ogni affermazione del case che fai, TypeScript può darti informazioni sulle opzioni disponibili. Provalo tu stesso. Se hai esaurito tutte le opzioni, TypeScript ti dirà nel tuo ramo default che ciò non potrà never accadere. Questo è letteralmente il tipo never , il che significa che potresti aver raggiunto uno stato di errore che devi gestire.

Questa è una categoria di errori in meno. Ora sappiamo esattamente quali possibili metodi HTTP sono disponibili.

Possiamo fare lo stesso per i codici di stato HTTP, definendo un sottoinsieme di numeri validi che statusCode può assumere:

 type StatusCode = 100 | 101 | 102 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 444 | 449 | 450 | 451 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 598 | 599; type ServerReply = { send: (obj?: any) => void; status: (statusCode: StatusCode) => ServerReply; };

Il tipo StatusCode è di nuovo un tipo di unione. E con ciò escludiamo un'altra categoria di errori. Improvvisamente, un codice del genere fallisce:

 app.get("/api/user/:userID", (req, res) => { if(req.method === "POS") { // ^^^^^^^^^^^^^^^^^^^ 'Methods' and '"POS"' have no overlap. res.status(20) // ^^ '20' is not assignable to parameter of type 'StatusCode' } })
E il nostro software diventa molto più sicuro! Ma possiamo fare di più!

Inserisci Generics

Quando definiamo un percorso con app.get , sappiamo implicitamente che l'unico metodo HTTP possibile è "GET" . Ma con le nostre definizioni di tipo, dobbiamo ancora verificare tutte le possibili parti dell'unione.

Il tipo per CallbackFn è corretto, poiché potremmo definire funzioni di callback per tutti i possibili metodi HTTP, ma se chiamiamo esplicitamente app.get , sarebbe bello salvare alcuni passaggi aggiuntivi che sono necessari solo per rispettare le digitazioni.

I generici TypeScript possono aiutare! I generici sono una delle principali funzionalità di TypeScript che consentono di ottenere il comportamento più dinamico dai tipi statici. In TypeScript in 50 lezioni, trascorriamo gli ultimi tre capitoli a scavare in tutte le complessità dei generici e nella loro funzionalità unica.

Quello che devi sapere in questo momento è che vogliamo definire ServerRequest in modo da poter specificare una parte di Methods invece dell'intero set. Per questo, utilizziamo la sintassi generica in cui possiamo definire i parametri come faremmo con le funzioni:

 type ServerRequest<Met extends Methods> = { method: Met; params: Record<string, string>; };

È questo che succede:

  1. ServerRequest diventa un tipo generico, come indicato dalle parentesi angolari
  2. Definiamo un parametro generico chiamato Met , che è un sottoinsieme di tipo Methods
  3. Usiamo questo parametro generico come variabile generica per definire il metodo.

Ti incoraggio anche a dare un'occhiata al mio articolo sulla denominazione di parametri generici.

Con quella modifica, possiamo specificare diversi ServerRequest senza duplicare le cose:

 type OnlyGET = ServerRequest<"GET">; type OnlyPOST = ServerRequest<"POST">; type POSTorPUT = ServerRquest<"POST" | "PUT">;

Dato che abbiamo modificato l'interfaccia di ServerRequest , dobbiamo apportare modifiche a tutti gli altri nostri tipi che utilizzano ServerRequest , come CallbackFn e la funzione get :

 type CallbackFn<Met extends Methods> = ( req: ServerRequest<Met>, reply: ServerReply ) => void; function get(path: string, callback: CallbackFn<"GET">) { // to be implemented }

Con la funzione get , passiamo un argomento effettivo al nostro tipo generico. Sappiamo che questo non sarà solo un sottoinsieme di Methods , sappiamo esattamente con quale sottoinsieme abbiamo a che fare.

Ora, quando utilizziamo app.get , abbiamo solo il possibile valore per req.method :

 app.get("/api/users/:userID", function (req, res) { req.method; // can only be get });

Ciò garantisce che non presupponiamo che metodi HTTP come "POST" o simili siano disponibili quando creiamo un callback app.get . Sappiamo esattamente con cosa abbiamo a che fare a questo punto, quindi riflettiamolo nei nostri tipi.

Abbiamo già fatto molto per assicurarci che request.method sia digitato in modo ragionevole e rappresenti lo stato attuale delle cose. Un bel vantaggio che otteniamo con il sottoinsieme del tipo di unione Methods è che possiamo creare una funzione di callback per scopi generici al di fuori di app.get che è indipendente dai tipi:

 const handler: CallbackFn<"PUT" | "POST"> = function(res, req) { res.method // can be "POST" or "PUT" }; const handlerForAllMethods: CallbackFn<Methods> = function(res, req) { res.method // can be all methods }; app.get("/api", handler); // ^^^^^^^ Nope, we don't handle "GET" app.get("/api", handlerForAllMethods); // This works

Digitando i parametri

Quello che non abbiamo ancora toccato è la digitazione dell'oggetto params . Finora, otteniamo un record che consente di accedere a ogni chiave string . Ora è nostro compito renderlo un po' più specifico!

Lo facciamo aggiungendo un'altra variabile generica. Uno per i metodi, uno per le possibili chiavi del nostro Record :

 type ServerRequest<Met extends Methods, Par extends string = string> = { method: Met; params: Record<Par, string>; };

La variabile di tipo generico Par può essere un sottoinsieme di tipo string e il valore predefinito è ogni stringa. Con ciò, possiamo dire a ServerRequest quali chiavi ci aspettiamo:

 // request.method = "GET" // request.params = { // userID: string // } type WithUserID = ServerRequest<"GET", "userID">

Aggiungiamo il nuovo argomento alla nostra funzione get e al tipo CallbackFn , così possiamo impostare i parametri richiesti:

 function get<Par extends string = string>( path: string, callback: CallbackFn<"GET", Par> ) { // to be implemented } type CallbackFn<Met extends Methods, Par extends string> = ( req: ServerRequest<Met, Par>, reply: ServerReply ) => void;

Se non impostiamo Par in modo esplicito, il tipo funziona come siamo abituati, poiché Par per impostazione predefinita è string . Se lo impostiamo, però, abbiamo improvvisamente una definizione corretta per l'oggetto req.params !

 app.get<"userID">("/api/users/:userID", function (req, res) { req.params.userID; // Works!! req.params.anythingElse; // doesn't work!! });

È fantastico! C'è una piccola cosa che può essere migliorata, però. Possiamo ancora passare ogni stringa all'argomento del path di app.get . Non sarebbe meglio se potessimo riflettere anche Par lì dentro?

Noi possiamo! Con il rilascio della versione 4.1, TypeScript è in grado di creare tipi letterali modello . Sintatticamente, funzionano proprio come i valori letterali del modello di stringa, ma a livello di tipo. Laddove siamo stati in grado di dividere la string impostata in sottoinsiemi con tipi letterali stringa (come abbiamo fatto con i metodi), i tipi letterali modello ci consentono di includere un intero spettro di stringhe.

Creiamo un tipo chiamato IncludesRouteParams , dove vogliamo assicurarci che Par sia correttamente incluso nel modo in stile Express di aggiungere due punti davanti al nome del parametro:

 type IncludesRouteParams<Par extends string> = | `${string}/:${Par}` | `${string}/:${Par}/${string}`;

Il tipo generico IncludesRouteParams accetta un argomento, che è un sottoinsieme di string . Crea un tipo di unione di due letterali modello:

  1. Il primo valore letterale del modello inizia con qualsiasi string , quindi include un carattere / seguito da un carattere : seguito dal nome del parametro. Questo assicura di catturare tutti i casi in cui il parametro si trova alla fine della stringa di percorso.
  2. Il secondo valore letterale del modello inizia con qualsiasi string , seguito dallo stesso modello di / , : e dal nome del parametro. Quindi abbiamo un altro carattere / , seguito da qualsiasi stringa. Questo ramo del tipo union si assicura di intercettare tutti i casi in cui il parametro si trova da qualche parte all'interno di un percorso.

Ecco come si comporta IncludesRouteParams con il nome del parametro userID con diversi casi di test:

 const a: IncludeRouteParams<"userID"> = "/api/user/:userID" // const a: IncludeRouteParams<"userID"> = "/api/user/:userID/orders" // const a: IncludeRouteParams<"userID"> = "/api/user/:userId" // const a: IncludeRouteParams<"userID"> = "/api/user" // const a: IncludeRouteParams<"userID"> = "/api/user/:userIDAndmore" //

Includiamo il nostro nuovo tipo di utilità nella dichiarazione della funzione get .

 function get<Par extends string = string>( path: IncludesRouteParams<Par>, callback: CallbackFn<"GET", Par> ) { // to be implemented } app.get<"userID">( "/api/users/:userID", function (req, res) { req.params.userID; // YEAH! } );

Grande! Otteniamo un altro meccanismo di sicurezza per assicurarci di non perdere l'aggiunta dei parametri al percorso effettivo! Quanto potente.

Legami generici

Ma indovina un po', non sono ancora soddisfatto. Ci sono alcuni problemi con questo approccio che diventano evidenti nel momento in cui i tuoi percorsi diventano un po' più complessi.

  1. Il primo problema che ho è che dobbiamo dichiarare esplicitamente i nostri parametri nel parametro di tipo generico. Dobbiamo associare Par a "userID" , anche se lo specificheremmo comunque nell'argomento percorso della funzione. Questo non è JavaScript-y!
  2. Questo approccio gestisce solo un parametro di percorso. Nel momento in cui aggiungiamo un'unione, ad esempio "userID" | "orderId" "userID" | "orderId" il controllo failsafe è soddisfatto con solo uno di questi argomenti disponibile. È così che funzionano i set. Può essere l'uno o l'altro.

Ci deve essere un modo migliore. E c'è. Altrimenti, questo articolo finirebbe con una nota molto amara.

Invertiamo l'ordine! Non proviamo a definire i parametri di percorso in una variabile di tipo generico, ma piuttosto estraiamo le variabili dal path che passiamo come primo argomento di app.get .

Per arrivare al valore effettivo, dobbiamo vedere come funziona l'associazione generica in TypeScript. Prendiamo ad esempio questa funzione di identity :

 function identity<T>(inp: T) : T { return inp }

Potrebbe essere la funzione generica più noiosa che tu abbia mai visto, ma illustra perfettamente un punto. identity accetta un argomento e restituisce di nuovo lo stesso input. Il tipo è il tipo generico T e restituisce anche lo stesso tipo.

Ora possiamo associare T a string , ad esempio:

 const z = identity<string>("yes"); // z is of type string

Questa associazione esplicitamente generica assicura che le strings passate solo a identity , e poiché ci leghiamo in modo esplicito, anche il tipo restituito è string . Se dimentichiamo di legare, succede qualcosa di interessante:

 const y = identity("yes") // y is of type "yes"

In tal caso, TypeScript deduce il tipo dall'argomento passato e associa T alla stringa di tipo letterale "yes" . Questo è un ottimo modo per convertire un argomento di funzione in un tipo letterale, che poi usiamo negli altri nostri tipi generici.

Facciamolo adattando app.get .

 function get<Path extends string = string>( path: Path, callback: CallbackFn<"GET", ParseRouteParams<Path>> ) { // to be implemented }

Rimuoviamo il tipo generico Par e aggiungiamo Path . Path può essere un sottoinsieme di qualsiasi string . Impostiamo path a questo tipo generico Path , il che significa che nel momento in cui passiamo un parametro per get , catturiamo il suo tipo letterale stringa. Passiamo Path a un nuovo tipo generico ParseRouteParams che non abbiamo ancora creato.

Lavoriamo su ParseRouteParams . Qui, invertiamo nuovamente l'ordine degli eventi. Invece di passare i parametri del percorso richiesti al generico per assicurarci che il percorso sia a posto, passiamo il percorso del percorso ed estraiamo i possibili parametri del percorso. Per questo, dobbiamo creare un tipo condizionale.

Tipi condizionali e tipi letterali di modelli ricorsivi

I tipi condizionali sono sintatticamente simili all'operatore ternario in JavaScript. Si verifica una condizione e, se la condizione è soddisfatta, si restituisce il ramo A, altrimenti si restituisce il ramo B. Ad esempio:

 type ParseRouteParams<Rte> = Rte extends `${string}/:${infer P}` ? P : never;

Qui controlliamo se Rte è un sottoinsieme di ogni percorso che termina con il parametro alla fine in stile Express (con un precedente "/:" ). Se è così, deduciamo questa stringa. Ciò significa che catturiamo il suo contenuto in una nuova variabile. Se la condizione è soddisfatta, restituiamo la stringa appena estratta, altrimenti non restituiamo mai, come in: “Non ci sono parametri di percorso”,

Se lo proviamo, otteniamo qualcosa del genere:

 type Params = ParseRouteParams<"/api/user/:userID"> // Params is "userID" type NoParams = ParseRouteParams<"/api/user"> // NoParams is never --> no params!

Ottimo, è già molto meglio di prima. Ora, vogliamo catturare tutti gli altri parametri possibili. Per questo, dobbiamo aggiungere un'altra condizione:

 type ParseRouteParams<Rte> = Rte extends `${string}/:${infer P}/${infer Rest}` ? P | ParseRouteParams<`/${Rest}`> : Rte extends `${string}/:${infer P}` ? P : never;

Il nostro tipo condizionale funziona ora come segue:

  1. Nella prima condizione, controlliamo se c'è un parametro di percorso da qualche parte tra il percorso. In tal caso, estraiamo sia il parametro route che tutto ciò che viene dopo. Restituiamo il parametro di percorso P appena trovato in un'unione in cui chiamiamo lo stesso tipo generico in modo ricorsivo con Rest . Ad esempio, se passiamo il percorso "/api/users/:userID/orders/:orderID" a ParseRouteParams , deduciamo "userID "userID" in P e "orders/:orderID" in Rest . Chiamiamo lo stesso tipo con Rest
  2. È qui che entra in gioco la seconda condizione. Qui controlliamo se è presente un tipo alla fine. Questo è il caso di "orders/:orderID" . "orderID" e restituiamo questo tipo letterale.
  3. Se non ci sono più parametri di percorso rimasti, non torneremo mai.

Dan Vanderkam mostra un tipo simile e più elaborato per ParseRouteParams , ma anche quello che vedi sopra dovrebbe funzionare. Se proviamo il nostro ParseRouteParams appena adattato, otteniamo qualcosa del genere:

 // Params is "userID" type Params = ParseRouteParams<"/api/user/:userID"> // MoreParams is "userID" | "orderID" type MoreParams = ParseRouteParams<"/api/user/:userID/orders/:orderId">

Applichiamo questo nuovo tipo e vediamo come appare il nostro utilizzo finale di app.get .

 app.get("/api/users/:userID/orders/:orderID", function (req, res) { req.params.userID; // YES!! req.params.orderID; // Also YES!!! });

Oh. Sembra proprio il codice JavaScript che avevamo all'inizio!

Tipi statici per comportamento dinamico

I tipi che abbiamo appena creato per una funzione app.get si assicurano di escludere un sacco di possibili errori:

  1. Possiamo passare solo codici di stato numerici corretti a res.status()
  2. req.method è una delle quattro possibili stringhe e quando utilizziamo app.get , sappiamo che è solo "GET"
  3. Possiamo analizzare i parametri del percorso e assicurarci di non avere errori di battitura all'interno della nostra richiamata

Se osserviamo l'esempio dall'inizio di questo articolo, otteniamo i seguenti messaggi di errore:

 app.get("/api/users/:userID", function(req, res) { if (req.method === "POST") { // ^^^^^^^^^^^^^^^^^^^^^ // This condition will always return 'false' // since the types '"GET"' and '"POST"' have no overlap. res.status(20).send({ // ^^ // Argument of type '20' is not assignable to // parameter of type 'StatusCode' message: "Welcome, user " + req.params.userId // ^^^^^^ // Property 'userId' does not exist on type // '{ userID: string; }'. Did you mean 'userID'? }); } })

E tutto questo prima di eseguire effettivamente il nostro codice! I server in stile Express sono un perfetto esempio della natura dinamica di JavaScript. A seconda del metodo che chiami, della stringa che passi per il primo argomento, molti comportamenti cambiano all'interno del callback. Prendi un altro esempio e tutti i tuoi tipi sembrano completamente diversi.

Ma con alcuni tipi ben definiti, possiamo catturare questo comportamento dinamico durante la modifica del nostro codice. In fase di compilazione con tipi statici, non in fase di esecuzione quando le cose vanno a gonfie vele!

E questo è il potere di TypeScript. Un sistema di tipo statico che cerca di formalizzare tutto il comportamento dinamico di JavaScript che tutti conosciamo così bene. Se vuoi provare l'esempio che abbiamo appena creato, vai al parco giochi di TypeScript e giocherellare con esso.


TypeScript in 50 lezioni di Stefan Baumgartner In questo articolo abbiamo toccato molti concetti. Se desideri saperne di più, dai un'occhiata a TypeScript in 50 lezioni, dove ottieni una delicata introduzione al sistema dei caratteri in lezioni piccole e facilmente digeribili. Le versioni di ebook sono immediatamente disponibili e il libro cartaceo sarà un ottimo riferimento per la tua libreria di codifica.