Saisie statique dynamique dans TypeScript

Publié: 2022-03-10
Résumé rapide ↬ Dans cet article, nous examinons certaines des fonctionnalités les plus avancées de TypeScript, telles que les types d'union, les types conditionnels, les types littéraux de modèle et les génériques. Nous voulons formaliser le comportement JavaScript le plus dynamique de manière à pouvoir détecter la plupart des bogues avant qu'ils ne surviennent. Nous appliquons plusieurs enseignements de tous les chapitres de TypeScript dans 50 leçons, un livre que nous avons publié ici sur Smashing Magazine fin 2020. Si vous souhaitez en savoir plus, assurez-vous de le consulter !

JavaScript est un langage de programmation intrinsèquement dynamique. En tant que développeurs, nous pouvons exprimer beaucoup de choses avec peu d'efforts, et le langage et son environnement d'exécution déterminent ce que nous avions l'intention de faire. C'est ce qui rend JavaScript si populaire auprès des débutants, et qui rend productifs les développeurs expérimentés ! Il y a cependant une mise en garde : nous devons être vigilants ! Erreurs, fautes de frappe, comportement correct du programme : beaucoup de choses se passent dans nos têtes !

Jetez un oeil à l'exemple suivant.

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

Nous avons un serveur https://expressjs.com/-style qui nous permet de définir une route (ou un chemin), et exécute un rappel si l'URL est demandée.

Le rappel prend deux arguments :

  1. L'objet de la request .
    Ici, nous obtenons des informations sur la méthode HTTP utilisée (par exemple GET, POST, PUT, DELETE) et les paramètres supplémentaires qui entrent. Dans cet exemple, l' userID doit être mappé à un paramètre userID qui, eh bien, contient l'ID de l'utilisateur !
  2. La response ou l'objet de reply .
    Ici, nous voulons préparer une réponse appropriée du serveur au client. Nous voulons envoyer les codes d'état corrects ( status la méthode) et envoyer la sortie JSON sur le fil.

Ce que nous voyons dans cet exemple est fortement simplifié, mais donne une bonne idée de ce que nous faisons. L'exemple ci-dessus est également truffé d'erreurs ! Regarde:

 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! Trois lignes de code d'implémentation et trois erreurs ? Que s'est-il passé?

  1. La première erreur est nuancée. Pendant que nous disons à notre application que nous voulons écouter les requêtes GET (d'où app.get ), nous ne faisons quelque chose que si la méthode de requête est POST . À ce stade particulier de notre application, req.method ne peut pas être POST . Nous n'enverrions donc jamais de réponse, ce qui pourrait entraîner des délais d'attente inattendus.
  2. C'est bien que nous envoyions explicitement un code de statut ! 20 n'est pas un code d'état valide, cependant. Les clients pourraient ne pas comprendre ce qui se passe ici.
  3. C'est la réponse que nous voulons renvoyer. Nous accédons aux arguments analysés mais avons une faute de frappe moyenne. C'est userID pas userId . Tous nos utilisateurs seraient accueillis par "Bienvenue, utilisateur non défini!". Quelque chose que vous avez certainement vu dans la nature !

Et des choses comme ça arrivent ! Surtout en JavaScript. Nous gagnons en expressivité – pas une seule fois nous n'avons eu à nous soucier des types – mais nous devons faire très attention à ce que nous faisons.

C'est également là que JavaScript reçoit beaucoup de réactions négatives de la part des programmeurs qui ne sont pas habitués aux langages de programmation dynamiques. Ils ont généralement des compilateurs qui les signalent d'éventuels problèmes et détectent les erreurs à l'avance. Ils peuvent sembler prétentieux lorsqu'ils désapprouvent la quantité de travail supplémentaire que vous devez faire dans votre tête pour vous assurer que tout fonctionne correctement. Ils pourraient même vous dire que JavaScript n'a pas de types. Ce qui n'est pas vrai.

Anders Hejlsberg, l'architecte principal de TypeScript, a déclaré dans son discours d'ouverture de MS Build 2017 que « ce n'est pas que JavaScript n'a pas de système de type. Il n'y a tout simplement aucun moyen de le formaliser ».

Et c'est le but principal de TypeScript. TypeScript veut comprendre votre code JavaScript mieux que vous. Et là où TypeScript ne peut pas comprendre ce que vous voulez dire, vous pouvez aider en fournissant des informations de type supplémentaires.

Plus après saut! Continuez à lire ci-dessous ↓

Dactylographie de base

Et c'est ce que nous allons faire maintenant. Prenons la méthode get de notre serveur de style Express et ajoutons suffisamment d'informations de type pour pouvoir exclure autant de catégories d'erreurs que possible.

Nous commençons par quelques informations de base sur le type. Nous avons un objet app qui pointe vers une fonction get . La fonction get prend path , qui est une chaîne, et un rappel.

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

Alors que string est un type de base, dit primitif , CallbackFn est un type composé que nous devons définir explicitement.

CallbackFn est un type de fonction qui prend deux arguments :

  • req , qui est de type ServerRequest
  • reply de type ServerReply

CallbackFn renvoie void .

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

ServerRequest est un objet assez complexe dans la plupart des frameworks. Nous faisons une version simplifiée à des fins de démonstration. Nous transmettons une chaîne de method , pour "GET" , "POST" , "PUT" , "DELETE" , etc. Il a également un enregistrement params . Les enregistrements sont des objets qui associent un ensemble de clés à un ensemble de propriétés. Pour l'instant, nous voulons permettre à chaque clé de string d'être mappée à une propriété de string . Nous refactorons celui-ci plus tard.

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

Pour ServerReply , nous présentons quelques fonctions, sachant qu'un véritable objet ServerReply a bien plus. Une fonction d' send prend un argument optionnel avec les données que nous voulons envoyer. Et nous avons la possibilité de définir un code d'état avec la fonction d' status .

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

C'est déjà quelque chose, et nous pouvons exclure quelques erreurs :

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

Mais nous pouvons toujours envoyer des codes d'état erronés (n'importe quel nombre est possible) et n'avons aucune idée des méthodes HTTP possibles (n'importe quelle chaîne est possible). Affinons nos types.

Ensembles plus petits

Vous pouvez voir les types primitifs comme un ensemble de toutes les valeurs possibles de cette certaine catégorie. Par exemple, string inclut toutes les chaînes possibles pouvant être exprimées en JavaScript, number inclut tous les nombres possibles avec une précision à double flottant. boolean inclut toutes les valeurs booléennes possibles, qui sont true et false .

TypeScript vous permet d'affiner ces ensembles en sous-ensembles plus petits. Par exemple, nous pouvons créer un type Method qui inclut toutes les chaînes possibles que nous pouvons recevoir pour les méthodes HTTP :

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

La Method est un ensemble plus petit du plus grand ensemble de string . Method est un type union de types littéraux. Un type littéral est la plus petite unité d'un ensemble donné. Une chaîne littérale. Un nombre littéral. Il n'y a pas d'ambiguïté. C'est juste "GET" . Vous les mettez dans une union avec d'autres types littéraux, créant un sous-ensemble de tous les types plus grands que vous avez. Vous pouvez également créer un sous-ensemble avec des types littéraux à la fois string et number , ou différents types d'objets composés. Il existe de nombreuses possibilités de combiner et de mettre des types littéraux dans des unions.

Cela a un effet immédiat sur le rappel de notre serveur. Du coup, nous pouvons différencier ces quatre méthodes (ou plus si nécessaire), et épuiser toutes les possibilités dans le code. TypeScript va nous 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; } });

Avec chaque déclaration de case que vous faites, TypeScript peut vous donner des informations sur les options disponibles. Essayez vous-même. Si vous avez épuisé toutes les options, TypeScript vous dira dans votre branche default que cela ne peut never arriver. C'est littéralement le type never , ce qui signifie que vous avez peut-être atteint un état d'erreur que vous devez gérer.

C'est une catégorie d'erreurs en moins. Nous savons maintenant exactement quelles méthodes HTTP possibles sont disponibles.

Nous pouvons faire de même pour les codes de statut HTTP, en définissant un sous-ensemble de nombres valides que statusCode peut prendre :

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

Le type StatusCode est à nouveau un type d'union. Et avec cela, nous excluons une autre catégorie d'erreurs. Soudain, un code comme celui-ci échoue :

 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' } })
Et notre logiciel devient beaucoup plus sûr ! Mais nous pouvons faire plus!

Entrez les génériques

Lorsque nous définissons une route avec app.get , nous savons implicitement que la seule méthode HTTP possible est "GET" . Mais avec nos définitions de type, nous devons toujours vérifier toutes les parties possibles de l'union.

Le type de CallbackFn est correct, car nous pourrions définir des fonctions de rappel pour toutes les méthodes HTTP possibles, mais si nous appelons explicitement app.get , il serait bien d'économiser quelques étapes supplémentaires qui ne sont nécessaires que pour se conformer aux typages.

Les génériques TypeScript peuvent vous aider ! Les génériques sont l'une des principales fonctionnalités de TypeScript qui vous permettent d'obtenir le comportement le plus dynamique des types statiques. Dans TypeScript en 50 leçons, nous passons les trois derniers chapitres à approfondir toutes les subtilités des génériques et leurs fonctionnalités uniques.

Ce que vous devez savoir maintenant, c'est que nous voulons définir ServerRequest de manière à pouvoir spécifier une partie des Methods au lieu de l'ensemble complet. Pour cela, on utilise la syntaxe générique où l'on peut définir des paramètres comme on le ferait avec des fonctions :

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

Voici ce qui se passe :

  1. ServerRequest devient un type générique, comme indiqué par les chevrons
  2. Nous définissons un paramètre générique appelé Met , qui est un sous-ensemble de type Methods
  3. Nous utilisons ce paramètre générique comme variable générique pour définir la méthode.

Je vous encourage également à consulter mon article sur la dénomination des paramètres génériques.

Avec ce changement, nous pouvons spécifier différents ServerRequest sans dupliquer les choses :

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

Depuis que nous avons changé l'interface de ServerRequest , nous devons apporter des modifications à tous nos autres types qui utilisent ServerRequest , comme CallbackFn et la fonction get :

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

Avec la fonction get , nous passons un argument réel à notre type générique. Nous savons qu'il ne s'agira pas simplement d'un sous-ensemble de Methods , nous savons exactement à quel sous-ensemble nous avons affaire.

Maintenant, lorsque nous utilisons app.get , nous n'avons qu'une valeur possible pour req.method :

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

Cela garantit que nous ne supposons pas que des méthodes HTTP telles que "POST" ou similaires sont disponibles lorsque nous créons un rappel app.get . Nous savons exactement à quoi nous avons affaire à ce stade, alors reflétons cela dans nos types.

Nous avons déjà fait beaucoup pour nous assurer que request.method est raisonnablement typé et représente l'état réel des choses. Un avantage intéressant que nous obtenons avec le sous-ensemble du type d'union Methods est que nous pouvons créer une fonction de rappel à usage général en dehors de app.get qui est de type sécurisé :

 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

Paramètres de saisie

Ce que nous n'avons pas encore touché, c'est taper l'objet params . Jusqu'à présent, nous obtenons un enregistrement qui permet d'accéder à chaque clé string . C'est à nous maintenant de rendre cela un peu plus précis !

Nous faisons cela en ajoutant une autre variable générique. Une pour les méthodes, une pour les clés possibles dans notre Record :

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

La variable de type générique Par peut être un sous-ensemble de type string et la valeur par défaut est chaque chaîne. Avec cela, nous pouvons dire à ServerRequest quelles clés nous attendons :

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

Ajoutons le nouvel argument à notre fonction get et au type CallbackFn , afin de pouvoir définir les paramètres demandés :

 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;

Si nous ne définissons pas Par explicitement, le type fonctionne comme nous en avons l'habitude, puisque Par défaut est string . Si nous le définissons cependant, nous avons soudainement une définition appropriée pour l'objet req.params !

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

C'est génial! Il y a cependant une petite chose qui peut être améliorée. Nous pouvons toujours passer chaque chaîne à l'argument path de app.get . Ne serait-il pas mieux si nous pouvions également refléter Par là-dedans ?

Nous pouvons! Avec la version 4.1, TypeScript est capable de créer des modèles de types littéraux . Syntaxiquement, ils fonctionnent comme des littéraux de modèle de chaîne, mais au niveau du type. Là où nous avons pu diviser la string définie en sous-ensembles avec des types littéraux de chaîne (comme nous l'avons fait avec les méthodes), les types littéraux de modèle nous permettent d'inclure un spectre complet de chaînes.

Créons un type appelé IncludesRouteParams , où nous voulons nous assurer que Par est correctement inclus dans la manière de style Express d'ajouter deux-points devant le nom du paramètre :

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

Le type générique IncludesRouteParams prend un argument, qui est un sous-ensemble de string . Il crée un type d'union de deux littéraux de modèle :

  1. Le premier modèle littéral commence par n'importe quelle string , puis inclut un caractère / suivi d'un caractère : , suivi du nom du paramètre. Cela garantit que nous interceptons tous les cas où le paramètre se trouve à la fin de la chaîne de routage.
  2. Le deuxième modèle littéral commence par n'importe quelle string , suivie du même modèle de / , : et du nom du paramètre. Ensuite, nous avons un autre caractère / , suivi de n'importe quelle chaîne. Cette branche du type union s'assure que nous interceptons tous les cas où le paramètre se trouve quelque part dans une route.

Voici comment IncludesRouteParams avec le nom de paramètre userID se comporte avec différents cas de 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" //

Incluons notre nouveau type d'utilitaire dans la déclaration de la fonction 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! } );

Génial! Nous obtenons un autre mécanisme de sécurité pour nous assurer que nous ne manquons pas d'ajouter les paramètres à l'itinéraire réel ! Quelle puissance.

Liaisons génériques

Mais devinez quoi, je ne suis toujours pas satisfait. Il y a quelques problèmes avec cette approche qui deviennent apparents au moment où vos itinéraires deviennent un peu plus complexes.

  1. Le premier problème que j'ai est que nous devons énoncer explicitement nos paramètres dans le paramètre de type générique. Nous devons lier Par à "userID" , même si nous le spécifierions de toute façon dans l'argument path de la fonction. Ce n'est pas JavaScript !
  2. Cette approche ne gère qu'un seul paramètre de route. Au moment où nous ajoutons une union, par exemple "userID" | "orderId" "userID" | "orderId" le contrôle de sécurité est satisfait avec un seul de ces arguments disponible. C'est ainsi que fonctionnent les ensembles. Cela peut être l'un ou l'autre.

Il doit y avoir un meilleur moyen. Et voici. Sinon, cet article se terminerait sur une note très amère.

Inversons l'ordre ! N'essayons pas de définir les paramètres de route dans une variable de type générique, mais extrayons plutôt les variables du path que nous passons comme premier argument de app.get .

Pour arriver à la valeur réelle, nous devons voir comment fonctionne la liaison générique dans TypeScript. Prenons cette fonction d' identity par exemple :

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

C'est peut-être la fonction générique la plus ennuyeuse que vous ayez jamais vue, mais elle illustre parfaitement un point. identity prend un argument et renvoie à nouveau la même entrée. Le type est le type générique T , et il renvoie également le même type.

Nous pouvons maintenant lier T à string , par exemple :

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

Cette liaison explicitement générique garantit que nous ne transmettons que strings à identity , et puisque nous nous lions explicitement, le type de retour est également string . Si nous oublions de lier, quelque chose d'intéressant se produit :

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

Dans ce cas, TypeScript déduit le type de l'argument que vous transmettez et lie T au type littéral de chaîne "yes" . C'est un excellent moyen de convertir un argument de fonction en un type littéral, que nous utilisons ensuite dans nos autres types génériques.

Faisons cela en adaptant app.get .

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

Nous supprimons le type générique Par et ajoutons Path . Path peut être un sous-ensemble de n'importe quelle string . Nous définissons path sur ce type générique Path , ce qui signifie qu'au moment où nous passons un paramètre à get , nous attrapons son type littéral de chaîne. Nous passons Path à un nouveau type générique ParseRouteParams que nous n'avons pas encore créé.

Travaillons sur ParseRouteParams . Ici, nous inversons à nouveau l'ordre des événements. Au lieu de transmettre les paramètres de route demandés au générique pour s'assurer que le chemin est correct, nous passons le chemin de route et extrayons les paramètres de route possibles. Pour cela, nous devons créer un type conditionnel.

Types conditionnels et types littéraux de modèles récursifs

Les types conditionnels sont syntaxiquement similaires à l'opérateur ternaire en JavaScript. Vous vérifiez une condition, et si la condition est remplie, vous retournez la branche A, sinon, vous retournez la branche B. Par exemple :

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

Ici, nous vérifions si Rte est un sous-ensemble de chaque chemin qui se termine par le paramètre à la fin de style Express (avec un "/:" précédent). Si oui, nous déduisons cette chaîne. Ce qui signifie que nous capturons son contenu dans une nouvelle variable. Si la condition est remplie, nous renvoyons la chaîne nouvellement extraite, sinon, nous ne renvoyons jamais, comme dans : "Il n'y a pas de paramètres de route",

Si nous l'essayons, nous obtenons quelque chose comme ça :

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

Super, c'est déjà bien mieux qu'avant. Maintenant, nous voulons capturer tous les autres paramètres possibles. Pour cela, nous devons ajouter une autre condition :

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

Notre type conditionnel fonctionne maintenant comme suit :

  1. Dans la première condition, nous vérifions s'il existe un paramètre de route quelque part entre la route. Si c'est le cas, nous extrayons à la fois le paramètre route et tout ce qui vient après. Nous renvoyons le paramètre de route nouvellement trouvé P dans une union où nous appelons le même type générique de manière récursive avec le Rest . Par exemple, si nous passons la route "/api/users/:userID/orders/:orderID" à ParseRouteParams , nous déduisons "userID" dans P et "orders/:orderID" dans Rest . Nous appelons le même type avec Rest
  2. C'est là qu'intervient la deuxième condition. Ici, nous vérifions s'il y a un type à la fin. C'est le cas pour "orders/:orderID" . Nous extrayons "orderID" et renvoyons ce type littéral.
  3. S'il ne reste plus de paramètre de route, nous renvoyons jamais.

Dan Vanderkam montre un type similaire et plus élaboré pour ParseRouteParams , mais celui que vous voyez ci-dessus devrait également fonctionner. Si nous essayons notre ParseRouteParams nouvellement adapté, nous obtenons quelque chose comme ceci :

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

Appliquons ce nouveau type et voyons à quoi ressemble notre utilisation finale de app.get .

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

Wow. Cela ressemble au code JavaScript que nous avions au début !

Types statiques pour le comportement dynamique

Les types que nous venons de créer pour une fonction app.get que nous excluons une tonne d'erreurs possibles :

  1. Nous ne pouvons transmettre que les codes d'état numériques appropriés à res.status()
  2. req.method est l'une des quatre chaînes possibles, et lorsque nous utilisons app.get , nous savons que ce n'est que "GET"
  3. Nous pouvons analyser les paramètres de route et nous assurer que nous n'avons pas de fautes de frappe dans notre rappel

Si nous regardons l'exemple du début de cet article, nous obtenons les messages d'erreur suivants :

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

Et tout cela avant d'exécuter réellement notre code ! Les serveurs de style Express sont un parfait exemple de la nature dynamique de JavaScript. Selon la méthode que vous appelez, la chaîne que vous transmettez pour le premier argument, de nombreux changements de comportement à l'intérieur du rappel. Prenez un autre exemple et tous vos types sont complètement différents.

Mais avec quelques types bien définis, nous pouvons détecter ce comportement dynamique lors de la modification de notre code. Au moment de la compilation avec des types statiques, pas au moment de l'exécution quand les choses explosent !

Et c'est la puissance de TypeScript. Un système de type statique qui tente de formaliser tout le comportement JavaScript dynamique que nous connaissons tous si bien. Si vous voulez essayer l'exemple que nous venons de créer, rendez-vous sur le terrain de jeu TypeScript et jouez avec.


TypeScript en 50 leçons par Stefan Baumgartner Dans cet article, nous avons abordé de nombreux concepts. Si vous souhaitez en savoir plus, consultez TypeScript en 50 leçons, où vous obtenez une introduction en douceur au système de type dans de petites leçons faciles à digérer. Les versions de livre électronique sont disponibles immédiatement et le livre imprimé constituera une excellente référence pour votre bibliothèque de codage.