Fermer

janvier 29, 2021

Typage statique dynamique dans TypeScript


À propos de l'auteur

Stefan Baumgartner est un architecte logiciel basé en Autriche. Il publie en ligne depuis la fin des années 1990, écrivant pour Manning, Smashing Magazine et A List…
En savoir plus sur
Stefan

Dans cet article, nous examinons certaines des fonctionnalités les plus avancées de TypeScript, telles que les types 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 en 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 vérifier!

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 exécution déterminent ce que nous avions l'intention de faire. C'est ce qui rend JavaScript si populaire pour les débutants et qui rend les développeurs expérimentés productifs! Il y a cependant une mise en garde: nous devons être vigilants! Erreurs, fautes de frappe, comportement correct du programme: une grande partie de cela se produit dans nos têtes!

Jetez un œil à 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 de style https://expressjs.com/ 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 request .
    Ici, nous obtenons des informations sur la méthode HTTP utilisée (par exemple GET, POST, PUT, DELETE) et des paramètres supplémentaires qui Dans cet exemple, userID doit être mappé à un paramètre userID qui contient bien l'ID de l'utilisateur!
  2. La réponse ou réponse objet.
    Ici, nous voulons préparer une réponse appropriée du serveur au client. Nous voulons envoyer des codes d'état corrects (méthode status ) 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! Jetez un oeil:

 app.get ("/ api / users /: userID", function (req, res) {
  if (req.method === "POST") {/ * Erreur 1 * /
    res.status (20) .send ({/ * Erreur 2 * /
      message: "Bienvenue, utilisateur" + req.params.userId / * Erreur 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. Alors 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 inattendus.
  2. Super que nous envoyions explicitement un code d'état! 20 n'est cependant pas un code d'état valide. Les clients peuvent 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. Il s’agit de userID et non de 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 devons faire très attention à ce que nous faisons.

C'est aussi là que JavaScript reçoit beaucoup de réactions négatives de la part de programmeurs qui ne sont pas habitués à la programmation dynamique langues. Ils ont généralement des compilateurs qui les signalent des problèmes possibles et détectent les erreurs à l'avance. Ils peuvent sembler arrogants quand ils froncent les sourcils sur la quantité de travail supplémentaire que vous devez faire dans votre tête pour s'assurer que tout fonctionne correctement. Ils pourraient même vous dire que JavaScript n'a aucun type. Ce qui n’est pas vrai.

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

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

Typage 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 que nous puissions exclure autant de catégories d’erreurs que possible.

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

 const app = {
  obtenir, / * publier, mettre, supprimer, ... à venir! * /
};

function get (chemin: chaîne, rappel: CallbackFn) {
  // à mettre en œuvre -> pas important pour le moment
}

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 qui est du 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 passons dans une chaîne 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é string d'être mappée à une propriété string . Nous refactoriserons celui-ci plus tard.

 type ServerRequest = {
  méthode: chaîne;
  paramètres: Record ;
};

Pour ServerReply nous présentons quelques fonctions, sachant qu'un véritable objet ServerReply a beaucoup plus. Une fonction 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 status .

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

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

 app.get ("/ api / users /: userID", function (req, res) {
  if (req.method === 2) {
// ^^^^^^^^^^^^^^^^^ 💥 Erreur, le numéro de type n'est pas attribuable à la chaîne

    res.status ("200"). send ()
// ^^^^^ 💥 Erreur, la chaîne de type n'est pas attribuable à nombre
  }
})

Mais nous pouvons toujours envoyer des codes de statut 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 qui peuvent être exprimées en JavaScript, number inclut tous les nombres possibles avec une précision à double virgule. 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" | "EFFACER";

type ServerRequest = {
  méthode: méthodes;
  paramètres: Record ;
};

La méthode est un ensemble plus petit de l'ensemble de cordes plus grand . La méthode est un type d'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 de chaîne et numéro ou différents types d'objets composés. Il y a beaucoup de possibilités pour combiner et mettre des types littéraux dans des unions.

Ceci a un effet immédiat sur notre rappel de serveur. Du coup, nous pouvons différencier ces quatre méthodes (ou plus si nécessaire), et épuiser toutes les possibilités du code. TypeScript nous guidera:

 app.get ("/ api / users /: userID", function (req, res) {
  // à ce stade, TypeScript sait que req.method
  // peut prendre l'une des quatre valeurs possibles
  commutateur (req.method) {
    case "GET":
      Pause;
    cas "POST":
      Pause;
    case "DELETE":
      Pause;
    case "PUT":
      Pause;
    défaut:
      // ici, req.method n'est jamais
      req.method;
  }
});

Avec chaque déclaration case que vous faites, TypeScript peut vous donner des informations sur les options disponibles. Essayez-le par vous-même . Si vous avez épuisé toutes les options, TypeScript vous dira dans votre branche default que cela ne peut jamais se produire. Il s’agit littéralement du type jamais 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 sont disponibles.

Nous pouvons faire de même pour les codes d'état 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 = {
  envoyer: (obj ?: any) => void;
  status: (statusCode: StatusCode) => ServerReply;
};

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") {
// ^^^^^^^^^^^^^^^^^^^^ 'Méthodes' et '"POS"' ne se chevauchent pas.
    statut res. (20)
// ^^ '20' n'est pas assignable au paramètre de type 'StatusCode'
 }
})

Et notre logiciel devient beaucoup plus sûr! Mais nous pouvons faire plus!

Enter Generics

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 appeler explicitement app.get ce serait bien de sauvegarder quelques étapes supplémentaires qui ne sont nécessaires que pour se conformer aux typages.

Les génériques TypeScript peuvent 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 in 50 Lessons nous passons les trois derniers chapitres à explorer 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 de Methods au lieu de l'ensemble complet. Pour cela, nous utilisons la syntaxe générique où nous pouvons définir des paramètres comme nous le ferions avec des fonctions:

 type ServerRequest  = {
  méthode: Met;
  paramètres: Record ;
};

Voici ce qui se passe:

  1. ServerRequest devient un type générique, comme indiqué par les crochets angulaires
  2. Nous définissons un paramètre générique appelé Met qui est un sous-ensemble de type Méthodes
  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 . [19659006] Avec ce changement, nous pouvons spécifier différents ServerRequest s sans dupliquer les choses:

 type OnlyGET = ServerRequest;
type OnlyPOST = ServerRequest;
type POSTorPUT = ServerRquest;

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

 type CallbackFn  = (
  req: ServerRequest ,
  réponse: ServerReply
) => vide;

function get (chemin: chaîne, rappel: CallbackFn <"GET">) {
  // à implémenter
}

Avec la fonction get nous passons un argument réel à notre type générique. Nous savons que ce ne sera pas simplement un sous-ensemble de Methods nous savons exactement de quel sous-ensemble nous avons affaire.

Maintenant, lorsque nous utilisons app.get nous ne faisons que ont une valeur possible pour req.method :

 app.get ("/ api / users /: userID", function (req, res) {
  req.method; // peut seulement être obtenu
});

Cela garantit que nous ne supposons pas que des méthodes HTTP telles que "POST" ou similaire sont disponibles lorsque nous créons un rappel app.get . Nous savons exactement de 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 de affaires. 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 de app.get qui est de type sécurisé:

 Gestionnaire de const: CallbackFn <"PUT" | "POST"> = function (res, req) {
  res.method // peut être "POST" ou "PUT"
};

const handlerForAllMethods: CallbackFn  = function (res, req) {
  res.method // peut être toutes les méthodes
};


app.get ("/ api", gestionnaire);
// ^^^^^^^ 💥 Non, nous ne gérons pas "GET"

app.get ("/ api", handlerForAllMethods); // 👍 Cela fonctionne

Paramètres de frappe

Ce que nous n’avons pas encore touché, c’est de taper l’objet params . Jusqu'à présent, nous obtenons un enregistrement qui permet d'accéder à chaque clé de chaîne . C'est notre tâche 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  = {
  méthode: Met;
  paramètres: Record ;
};

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 = {
// ID utilisateur: chaîne
//}
type WithUserID = ServerRequest

Ajoutons le nouvel argument à notre fonction get et au type CallbackFn afin que nous puissions définir les paramètres demandés:

 function get  (
  chemin: chaîne,
  rappel: CallbackFn <"GET", Par>
) {
  // à implémenter
}

type CallbackFn  = (
  req: ServerRequest ,
  réponse: ServerReply
) => vide;

Si nous ne définissons pas explicitement Par le type fonctionne comme nous en avons l'habitude, puisque Par prend la valeur par défaut de string . Si nous le définissons cependant, nous avons soudainement une définition correcte pour l'objet req.params !

 app.get <"userID"> ("/ api / users /: userID", function (req, res ) {
  req.params.userID; // Travaux!!
  req.params.anythingElse; // 💥 ne fonctionne pas !!
});

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 y inclure également Par ?

Nous le pouvons! Avec la sortie de la version 4.1, TypeScript est capable de créer des types littéraux de modèle . Syntaxiquement, ils fonctionnent comme les littéraux de modèle de chaîne, mais au niveau du type. Là où nous avons pu diviser l'ensemble chaîne en sous-ensembles avec des types littéraux de chaîne (comme nous l'avons fait avec les méthodes), les types littéraux modèles nous permettent d'inclure tout un spectre de chaînes. [19659006] 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 un deux-points devant le nom du paramètre: [19659007] type includesRouteParams =
| `$ {chaîne} /: $ {Par}`
| `$ {chaîne} /: $ {Par} / $ {chaîne}`;

Le type générique IncludesRouteParams prend un argument, qui est un sous-ensemble de la chaîne . Il crée un type d'union de deux littéraux de gabarit:

  1. Le premier littéral de gabarit commence par n'importe quelle chaîne puis inclut un caractère / suivi d'un : suivi du nom du paramètre. Cela garantit que nous interceptons tous les cas où le paramètre est à la fin de la chaîne de route.
  2. Le deuxième littéral de modèle commence par any string suivi du même modèle de / : et le nom du paramètre. Ensuite, nous avons un autre caractère / suivi de n'importe quelle chaîne . Cette branche du type union garantit que nous interceptons tous les cas où le paramètre est 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 = "/ api / user /: userID" // 👍
const a: IncludeRouteParams = "/ api / user /: userID / orders" // 👍
const a: IncludeRouteParams = "/ api / user /: userId" // 💥
const a: IncludeRouteParams = "/ api / user" // 💥
const a: IncludeRouteParams = "/ api / user /: userIDAndmore" // 💥

Incluons notre nouveau type d’utilitaire dans la déclaration de la fonction get .

 function get  (
  chemin: includesRouteParams ,
  rappel: CallbackFn <"GET", Par>
) {
  // à implémenter
}

app.get <"userID"> (
  "/ api / users /: userID",
  function (req, res) {
    req.params.userID; // OUAIS!
  }
);

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

Fixations 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 routes deviennent un peu plus complexes.

  1. Le premier problème que j'ai est que nous devons déclarer 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 quand même dans l'argument path de la fonction. Ce n'est pas JavaScript-y!
  2. Cette approche ne gère qu'un seul paramètre d'itinéraire. Au moment où nous ajoutons une union, par exemple "userID" | "orderId" la vérification de sécurité est satisfaite avec seulement un de ces arguments étant disponible. Voilà comment fonctionnent les décors. 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 chemin que nous passons comme premier argument de app.get .

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

 function identity  (inp: T): T {
  retour 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: [19659007] const z = identité ("oui"); // z est de type chaîne

Cette liaison explicitement générique garantit que nous ne transmettons que les chaînes à identity et puisque 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 est de type "yes"

Dans ce cas, TypeScript déduit le type de l'argument que vous passez 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  ] (
  chemin: chemin,
  rappel: CallbackFn <"GET", ParseRouteParams >
) {
  // à implémenter
}

Nous supprimons le type générique Par et ajoutons Path . Path peut être un sous-ensemble de n'importe quelle chaîne . 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 capturons 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 passer les paramètres de route demandés au générique pour nous 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èle récursif

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 étend `$ {string} /: $ {infer P}`
  ? P
  : jamais;

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 "/:" ). Si tel est le cas, nous déduisons cette chaîne. Ce qui signifie que nous capturons son contenu dans une nouvelle variable. Si la condition est remplie, nous retournons la chaîne nouvellement extraite, sinon, nous retournons jamais, comme dans: "Il n'y a pas de paramètres d'itinéraire",

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

 type Params = ParseRouteParams <"/api/user/:userID"> // Le paramètre est "userID"

type NoParams = ParseRouteParams <"/api/user"> // NoParams n'est jamais -> pas de paramètres!

Génial, c'est déjà bien mieux que ce que nous faisions auparavant. Maintenant, nous voulons attraper tous les autres paramètres possibles. Pour cela, nous devons ajouter une autre condition:

 type ParseRouteParams  = Rte étend `$ {string} /: $ {infer P} / $ {infer Rest}`
  ? P | ParseRouteParams <`/${Rest}`>
  : Rte étend `$ {string} /: $ {infer P}`
  ? P
  : jamais;

Notre type conditionnel fonctionne maintenant comme suit:

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

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

 // Params est "userID"
type Params = ParseRouteParams 

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; // OUI!!
  req.params.orderID; // Aussi OUI !!!
});

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

Types statiques pour un comportement dynamique

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

  1. Nous ne pouvons transmettre les codes d'état numériques appropriés qu'à 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 d'itinéraire 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") {
// ^^^^^^^^^^^^^^^^^^^^^
// Cette condition retournera toujours 'false'
// puisque les types '"GET"' et '"POST"' ne se chevauchent pas.
    res.status (20) .send ({
// ^^
// L'argument de type '20' n'est pas assignable à
// paramètre de type 'StatusCode'
      message: "Bienvenue, utilisateur" + req.params.userId
// ^^^^^^
// La propriété 'userId' n'existe pas sur le type
// '{ID utilisateur: chaîne; } '. Vouliez-vous dire 'userID'?
    });
  }
})

Et tout cela avant de lancer 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 passez pour le premier argument, de nombreux changements de comportement dans le rappel. Prenons 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 l'édition de notre code. Au moment de la compilation avec les types statiques, pas au moment de l'exécution quand les choses tournent en plein essor!

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 manipulez-le.


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

 Smashing Editorial "width =" 35 "height =" 46 "loading =" lazy "decoding =" async (vf , il)




Source link