Tastare dinamică statică în TypeScript

Publicat: 2022-03-10
Rezumat rapid ↬ În acest articol, ne uităm la unele dintre caracteristicile mai avansate ale TypeScript, cum ar fi tipurile de uniuni, tipurile condiționale, tipurile literale de șablon și generice. Dorim să oficializăm cel mai dinamic comportament JavaScript într-un mod în care să putem detecta majoritatea erorilor înainte ca acestea să apară. Aplicam mai multe învățăminte din toate capitolele din TypeScript în 50 de lecții, o carte pe care am publicat-o aici pe Smashing Magazine la sfârșitul anului 2020. Dacă sunteți interesat să aflați mai multe, asigurați-vă că o verificați!

JavaScript este un limbaj de programare inerent dinamic. Noi, în calitate de dezvoltatori, putem exprima multe cu puțin efort, iar limbajul și timpul său de execuție își dă seama ce intenționăm să facem. Acesta este ceea ce face ca JavaScript să fie atât de popular pentru începători și care face ca dezvoltatorii experimentați să fie productivi! Există totuși un avertisment: trebuie să fim atenți! Greșeli, greșeli de scriere, comportamentul corect al programului: multe dintre acestea se întâmplă în capul nostru!

Aruncă o privire la următorul exemplu.

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

Avem un server https://expressjs.com/-style care ne permite să definim o rută (sau cale) și execută un callback dacă adresa URL este solicitată.

Callback-ul are două argumente:

  1. Obiectul request .
    Aici obținem informații despre metoda HTTP utilizată (de exemplu, GET, POST, PUT, DELETE) și parametri suplimentari care vin. În acest exemplu, userID ar trebui să fie mapat la un parametru userID care, bine, conține ID-ul utilizatorului!
  2. Obiectul de response sau reply .
    Aici dorim să pregătim un răspuns adecvat de la server către client. Dorim să trimitem coduri de stare corecte ( status metodei) și să trimitem ieșire JSON prin cablu.

Ceea ce vedem în acest exemplu este mult simplificat, dar oferă o idee bună despre ce facem. Exemplul de mai sus este, de asemenea, plin de erori! Uită-te:

 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! Trei linii de cod de implementare și trei erori? Ce s-a întâmplat?

  1. Prima eroare este nuanțată. În timp ce spunem aplicației noastre că vrem să ascultăm cererile GET (de aici app.get ), facem ceva doar dacă metoda de solicitare este POST . În acest moment al aplicației noastre, req.method nu poate fi POST . Așa că nu vom trimite niciodată niciun răspuns, ceea ce ar putea duce la expirări neașteptate.
  2. Foarte bine că trimitem în mod explicit un cod de stare! 20 nu este un cod de stare valid, totuși. Clienții s-ar putea să nu înțeleagă ce se întâmplă aici.
  3. Acesta este răspunsul pe care vrem să-l trimitem înapoi. Accesăm argumentele analizate, dar avem o greșeală de tipar. Este userID nu userId . Toți utilizatorii noștri vor fi întâmpinați cu „Bun venit, utilizator nedefinit!”. Ceva pe care cu siguranță ai văzut în sălbăticie!

Și se întâmplă astfel de lucruri! Mai ales în JavaScript. Dobândim expresivitate – nu o dată a trebuit să ne deranjez în privința tipurilor – dar trebuie să fim foarte atenți la ceea ce facem.

Aici este și locul în care JavaScript primește o mulțime de reacții de la programatori care nu sunt obișnuiți cu limbaje de programare dinamică. De obicei, au compilatoare care le indică posibile probleme și detectează erorile din timp. S-ar putea să devină aiurea când se încruntă la cantitatea de muncă suplimentară pe care trebuie să o faci în capul tău pentru a te asigura că totul funcționează corect. S-ar putea chiar să vă spună că JavaScript nu are tipuri. Ceea ce nu este adevărat.

Anders Hejlsberg, arhitectul principal al TypeScript, a spus în discursul său MS Build 2017 că „ nu înseamnă că JavaScript nu are un sistem de tipări. Pur și simplu nu există nicio modalitate de a o oficializa ”.

Și acesta este scopul principal al TypeScript. TypeScript vrea să înțeleagă codul JavaScript mai bine decât tine. Și acolo unde TypeScript nu poate înțelege ce vrei să spui, poți ajuta oferind informații suplimentare despre tip.

Mai multe după săritură! Continuați să citiți mai jos ↓

Tastarea de bază

Și asta este ceea ce vom face chiar acum. Să luăm metoda get de pe serverul nostru în stil Express și să adăugăm suficiente informații de tip, astfel încât să putem exclude cât mai multe categorii de erori posibil.

Începem cu câteva informații de tip de bază. Avem un obiect app care indică o funcție get . Funcția get ia path , care este un șir și un callback.

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

În timp ce string este un tip de bază, așa-numitul primitiv , CallbackFn este un tip compus pe care trebuie să-l definim în mod explicit.

CallbackFn este un tip de funcție care ia două argumente:

  • req , care este de tip ServerRequest
  • reply care este de tip ServerReply

CallbackFn returnează void .

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

ServerRequest este un obiect destul de complex în majoritatea cadrelor. Facem o versiune simplificată în scop demonstrativ. Trecem un șir de method , pentru "GET" , "POST" , "PUT" , "DELETE" , etc. Are și o înregistrare de params . Înregistrările sunt obiecte care asociază un set de chei cu un set de proprietăți. Pentru moment, dorim să permitem ca fiecare cheie string să fie mapată la o proprietate string . Pe acesta îl refactorăm mai târziu.

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

Pentru ServerReply , prezentăm câteva funcții, știind că un obiect ServerReply real are mult mai multe. O funcție de send ia un argument opțional cu datele pe care dorim să le trimitem. Și avem posibilitatea de a seta un cod de status cu funcția de stare.

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

Este deja ceva și putem exclude câteva erori:

 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 } })

Dar încă putem trimite coduri de stare greșite (orice număr este posibil) și nu avem nicio idee despre posibilele metode HTTP (orice șir este posibil). Să ne rafinăm tipurile.

Seturi mai mici

Puteți vedea tipurile primitive ca un set de toate valorile posibile ale acelei categorii. De exemplu, string include toate șirurile posibile care pot fi exprimate în JavaScript, number include toate numerele posibile cu precizie dublă. boolean include toate valorile booleene posibile, care sunt true și false .

TypeScript vă permite să rafinați acele seturi la subseturi mai mici. De exemplu, putem crea un tip Method care include toate șirurile posibile pe care le putem primi pentru metodele HTTP:

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

Method este un set mai mic din setul de string mai mare. Method este un tip de uniune de tipuri literale. Un tip literal este cea mai mică unitate dintr-o mulțime dată. Un șir literal. Un număr literal. Nu există ambiguitate. Este doar "GET" . Le puneți într-o uniune cu alte tipuri literale, creând un subset de tipuri mai mari pe care le aveți. De asemenea, puteți face un subset cu tipuri literale atât de string , cât și de number sau diferite tipuri de obiecte compuse. Există o mulțime de posibilități de a combina și de a pune tipuri literale în uniuni.

Acest lucru are un efect imediat asupra apelului înapoi pe serverul nostru. Dintr-o dată, putem diferenția între cele patru metode (sau mai multe dacă este necesar) și putem epuiza toate posibilitățile din cod. TypeScript ne va ghida:

 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; } });

Cu fiecare declarație de case pe care o faceți, TypeScript vă poate oferi informații despre opțiunile disponibile. Încercați-l singur. Dacă ați epuizat toate opțiunile, TypeScript vă va spune în ramura dvs. default că acest lucru nu se poate întâmpla never . Acesta este literalmente tipul never , ceea ce înseamnă că probabil ați ajuns la o stare de eroare pe care trebuie să o gestionați.

Aceasta este o categorie de erori mai puțin. Știm acum exact ce metode HTTP posibile sunt disponibile.

Putem face același lucru pentru codurile de stare HTTP, prin definirea unui subset de numere valide pe care statusCode le poate lua:

 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; };

Tip StatusCode este din nou un tip de uniune. Și cu asta, excludem o altă categorie de erori. Dintr-o dată, un astfel de cod eșuează:

 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' } })
Iar software-ul nostru devine mult mai sigur! Dar putem face mai mult!

Introduceți generice

Când definim o rută cu app.get , știm implicit că singura metodă HTTP posibilă este "GET" . Dar, cu definițiile noastre de tip, mai trebuie să verificăm toate părțile posibile ale uniunii.

Tipul pentru CallbackFn este corect, deoarece am putea defini funcții de apel invers pentru toate metodele HTTP posibile, dar dacă apelăm în mod explicit app.get , ar fi bine să salvăm câțiva pași suplimentari care sunt necesari doar pentru a respecta tastările.

Genericele TypeScript vă pot ajuta! Genericurile sunt una dintre caracteristicile majore din TypeScript care vă permit să obțineți cel mai dinamic comportament din tipurile statice. În TypeScript în 50 de lecții, petrecem ultimele trei capitole cercetând toate complexitățile genericelor și funcționalitatea lor unică.

Ceea ce trebuie să știți acum este că vrem să definim ServerRequest într-un mod în care să putem specifica o parte din Methods în loc de întregul set. Pentru asta, folosim sintaxa generică în care putem defini parametrii așa cum am face cu funcțiile:

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

Asta se intampla:

  1. ServerRequest devine un tip generic, așa cum este indicat de parantezele unghiulare
  2. Definim un parametru generic numit Met , care este un subset de tip Methods
  3. Folosim acest parametru generic ca variabilă generică pentru a defini metoda.

De asemenea, vă încurajez să consultați articolul meu despre denumirea parametrilor generici.

Cu această modificare, putem specifica diferite ServerRequest fără a duplica lucruri:

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

Deoarece am schimbat interfața ServerRequest , trebuie să facem modificări tuturor celorlalte tipuri care folosesc ServerRequest , cum ar fi CallbackFn și funcția get :

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

Cu funcția get , trecem un argument real tipului nostru generic. Știm că acesta nu va fi doar un subset de Methods , știm exact cu ce subset avem de-a face.

Acum, când folosim app.get , avem doar o valoare posibilă pentru req.method :

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

Acest lucru ne asigură că nu presupunem că metode HTTP precum "POST" sau similare sunt disponibile atunci când creăm un apel invers app.get . Știm exact cu ce avem de-a face în acest moment, așa că să reflectăm asta în tipurile noastre.

Am făcut deja multe pentru a ne asigura că request.method este scrisă în mod rezonabil și reprezintă starea reală a lucrurilor. Un beneficiu frumos pe care îl obținem prin subsetarea tipului de unire Methods este că putem crea o funcție de apel invers pentru uz general în afara app.get , care este sigură pentru tip:

 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

Tastarea Params

Ceea ce nu ne-am atins încă este să tastăm obiectul params . Până acum, obținem o înregistrare care permite accesarea fiecărei chei string . Este sarcina noastră acum să facem asta un pic mai specific!

Facem asta adăugând o altă variabilă generică. Una pentru metode, una pentru cheile posibile din Record noastră:

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

Variabila de tip generic Par poate fi un subset de tip string , iar valoarea implicită este fiecare șir. Cu asta, putem spune ServerRequest ce chei așteptăm:

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

Să adăugăm noul argument la funcția noastră get și la tipul CallbackFn , astfel încât să putem seta parametrii solicitați:

 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;

Dacă nu setăm Par în mod explicit, tipul funcționează așa cum ne-am obișnuit, deoarece Par implicit este string . Totuși, dacă îl setăm, avem dintr-o dată o definiție adecvată pentru obiectul req.params !

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

Grozav! Există totuși un lucru mic care poate fi îmbunătățit. Încă putem trece fiecare șir la argumentul path al app.get . N-ar fi mai bine dacă am putea reflecta și Par acolo?

Putem! Odată cu lansarea versiunii 4.1, TypeScript este capabil să creeze tipuri literale de șablon . Sintactic, ele funcționează la fel ca șirurile de caractere șablon, dar la nivel de tip. Acolo unde am putut împărți string setat în subseturi cu tipuri literale de șir (cum am făcut cu Metode), tipurile literale de șablon ne permit să includem un întreg spectru de șiruri.

Să creăm un tip numit IncludesRouteParams , unde vrem să ne asigurăm că Par este inclus în mod corespunzător în modul în stil Express de a adăuga două puncte în fața numelui parametrului:

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

Tipul generic IncludesRouteParams ia un argument, care este un subset de string . Se creează un tip de unire a două literale șablon:

  1. Primul literal șablon începe cu orice string , apoi include un caracter / urmat de un caracter : , urmat de numele parametrului. Acest lucru ne asigură că prindem toate cazurile în care parametrul se află la sfârșitul șirului de rută.
  2. Al doilea literal șablon începe cu orice string , urmat de același model de / , : și numele parametrului. Apoi avem un alt caracter / , urmat de orice șir. Această ramură de tip unire se asigură că prindem toate cazurile în care parametrul se află undeva într-o rută.

Acesta este modul în care IncludesRouteParams cu numele parametrului userID se comportă cu diferite cazuri de testare:

 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" //

Să includem noul nostru tip de utilitar în declarația funcției 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! } );

Grozav! Avem un alt mecanism de siguranță pentru a ne asigura că nu pierdem adăugarea parametrilor la traseul propriu-zis! Cât de puternic.

Legături generice

Dar ghici ce, încă nu sunt mulțumit de asta. Există câteva probleme cu această abordare care devin evidente în momentul în care rutele dvs. devin puțin mai complexe.

  1. Prima problemă pe care o am este că trebuie să ne precizăm în mod explicit parametrii în parametrul de tip generic. Trebuie să legăm Par la "userID" , chiar dacă l-am specifica oricum în argumentul cale al funcției. Acesta nu este JavaScript-y!
  2. Această abordare gestionează doar un parametru de rută. În momentul în care adăugăm o uniune, de exemplu "userID" | "orderId" "userID" | "orderId" verificarea de siguranță este satisfăcută cu doar unul dintre aceste argumente fiind disponibil. Așa funcționează seturile. Poate fi unul, sau altul.

Trebuie să existe o cale mai bună. Si aici este. Altfel, acest articol s-ar termina cu o notă foarte amară.

Să inversăm ordinea! Să nu încercăm să definim parametrii rutei într-o variabilă de tip generic, ci mai degrabă să extragem variabilele din path pe care o trecem ca prim argument al app.get .

Pentru a ajunge la valoarea reală, trebuie să vedem cum funcționează legarea generică în TypeScript. Să luăm de exemplu această funcție de identity :

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

Poate fi cea mai plictisitoare funcție generică pe care ați văzut-o vreodată, dar ilustrează perfect un punct. identity ia un argument și returnează din nou aceeași intrare. Tipul este tipul generic T și returnează, de asemenea, același tip.

Acum putem lega T de string , de exemplu:

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

Această legare generică în mod explicit se asigură că transmitem numai strings de caractere către identity și, deoarece legăm explicit, tipul returnat este, de asemenea, string . Dacă uităm să legăm, se întâmplă ceva interesant:

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

În acest caz, TypeScript deduce tipul din argumentul pe care îl transmiteți și leagă T de tipul literal șir "yes" . Aceasta este o modalitate excelentă de a converti un argument de funcție într-un tip literal, pe care apoi îl folosim în celelalte tipuri generice ale noastre.

Să facem asta adaptând app.get .

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

Îndepărtăm tipul generic Par și adăugăm Path . Path poate fi un subset al oricărui string . Setăm path acestui tip generic Path , ceea ce înseamnă că în momentul în care trecem un parametru pentru a get , prindem tipul său literal șir. Trecem Path către un nou tip generic ParseRouteParams pe care nu l-am creat încă.

Să lucrăm la ParseRouteParams . Aici, schimbăm din nou ordinea evenimentelor. În loc să trecem parametrii de rută solicitați la generic pentru a ne asigura că calea este în regulă, trecem calea rutei și extragem parametrii de rută posibili. Pentru aceasta, trebuie să creăm un tip condiționat.

Tipuri condiționale și tipuri de șabloane recursive literale

Tipurile condiționate sunt similare sintactic cu operatorul ternar din JavaScript. Verificați o condiție, iar dacă condiția este îndeplinită, returnați filiala A, în caz contrar, returnați filiala B. De exemplu:

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

Aici, verificăm dacă Rte este un subset al fiecărei căi care se termină cu parametrul la sfârșitul stil Express (cu un "/:" precedent). Dacă da, deducem acest șir. Ceea ce înseamnă că îi captăm conținutul într-o nouă variabilă. Dacă condiția este îndeplinită, returnăm șirul nou extras, în caz contrar, nu revenim niciodată, ca în: „Nu există parametri de rută”,

Dacă îl încercăm, obținem ceva de genul:

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

Grozav, asta e deja mult mai bine decât am făcut-o mai devreme. Acum, vrem să prindem toți ceilalți parametri posibili. Pentru asta, trebuie să adăugăm o altă condiție:

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

Tipul nostru condiționat funcționează acum după cum urmează:

  1. În prima condiție, verificăm dacă există un parametru de rută undeva între rută. Dacă da, extragem atât parametrul rutei, cât și tot ce urmează după aceea. Returnăm parametrul rută P nou găsit într-o uniune în care apelăm recursiv același tip generic cu Rest . De exemplu, dacă trecem ruta "/api/users/:userID/orders/:orderID" la ParseRouteParams , deducem "userID" în P și "orders/:orderID" în Rest . Numim același tip cu Rest
  2. Aici intervine a doua condiție. Aici verificăm dacă există un tip la sfârșit. Acesta este cazul pentru "orders/:orderID" . Extragem "orderID" și returnăm acest tip literal.
  3. Dacă nu mai rămâne niciun parametru de rută, nu ne întoarcem niciodată.

Dan Vanderkam arată un tip similar și mai elaborat pentru ParseRouteParams , dar și cel pe care îl vedeți mai sus ar trebui să funcționeze. Dacă încercăm ParseRouteParams nou adaptat, obținem ceva de genul acesta:

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

Să aplicăm acest nou tip și să vedem cum arată utilizarea noastră finală a app.get .

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

Wow. Acesta arată ca codul JavaScript pe care l-am avut la început!

Tipuri statice pentru comportament dinamic

Tipurile pe care tocmai le-am creat pentru o funcție app.get se asigură că excludem o mulțime de erori posibile:

  1. Putem transmite doar codurile numerice adecvate de stare către res.status()
  2. req.method este unul dintre cele patru șiruri posibile, iar când folosim app.get , știm că este doar "GET"
  3. Putem analiza parametrii rutei și să ne asigurăm că nu avem greșeli de scriere în apelul înapoi

Dacă ne uităm la exemplul de la începutul acestui articol, primim următoarele mesaje de eroare:

 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'? }); } })

Și toate acestea înainte de a rula codul nostru! Serverele în stil expres sunt un exemplu perfect al naturii dinamice a JavaScript. În funcție de metoda pe care o apelați, șirul pe care îl transmiteți pentru primul argument, multe schimbări de comportament în interiorul callback-ului. Luați un alt exemplu și toate tipurile dvs. arată complet diferit.

Dar cu câteva tipuri bine definite, putem surprinde acest comportament dinamic în timp ce ne edităm codul. În timpul compilării cu tipuri statice, nu în timpul execuției, când lucrurile merg în plină expansiune!

Și aceasta este puterea TypeScript. Un sistem de tip static care încearcă să oficializeze tot comportamentul dinamic JavaScript pe care îl cunoaștem cu toții atât de bine. Dacă doriți să încercați exemplul pe care tocmai l-am creat, mergeți la locul de joacă TypeScript și jucați-vă cu el.


TypeScript în 50 de lecții de Stefan Baumgartner În acest articol, am atins multe concepte. Dacă doriți să aflați mai multe, consultați TypeScript în 50 de lecții, unde veți primi o introducere blândă în sistemul de tipărire în lecții mici, ușor de digerat. Versiunile de cărți electronice sunt disponibile imediat, iar cartea tipărită va fi o referință excelentă pentru biblioteca dvs. de codare.