strictBindCallApplyTypeScript 3.2 introduit une vérification plus stricte de bind, call et apply. Mais qu'est ce que cela signifie en réalité ?
En JavaScript, bind, apply et call sont des méthodes sur des fonctions qui nous permettent de faire des choses comme lier this, appliquer partiellement des arguments, appeler des fonctions ayant une valeur différente pour this et appeler des fonctions ayant un tableau en arguments.
Malheureusement, à ses débuts, TypeScript n’était pas en mesure de modéliser ces fonctions, et bind, call et apply étaient tous typés pour prendre un nombre quelconque d'arguments et renvoyer any. De plus, les fonctions fléchées et les arguments rest / spread de ES2015 ont fourni à Microsoft une nouvelle syntaxe qui selon l’éditeur, permet d’exprimer plus facilement ce que certaines de ces méthodes font - et de manière plus efficace.
Rappelons qu’une expression de fonction fléchée (arrow function en anglais) permet d'avoir une syntaxe plus courte que les expressions de fonction et ne possède pas ses propres valeurs pour this, arguments, super, ou new.target. Les fonctions fléchées sont souvent anonymes et ne sont pas destinées à être utilisées pour déclarer des méthodes.
Néanmoins, suite à la demande, Microsoft a été conduit à revoir le sujet récemment. L’éditeur affirme avoir réalisé que deux fonctionnalités ouvraient les bonnes abstractions pour typer avec précision bind, call et apply sans codage en dur:
- this type de paramètre à partir de TypeScript 2.0
- Modélisation des listes de paramètres avec des types de n-uplets à partir de TypeScript 3.0
L’équipe explique que « La combinaison de ces deux éléments permet de s'assurer que nos utilisations de bind, call et apply sont plus strictement vérifiées lorsque nous utilisons un nouvel indicateur appelé strictBindCallApply. Lors de l'utilisation de ce nouvel indicateur, les méthodes sur les objets appelables sont décrites par un nouveau type global appelé CallableFunction, qui déclare des versions plus strictes des signatures pour bind, call et apply. De même, toutes les méthodes sur des objets constructibles (mais non appelables) sont décrites par un nouveau type global appelé NewableFunction ».
À titre d'exemple, nous pouvons voir comment Function.prototype.apply agit sous ce comportement:
| Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 | function foo(a: number, b: string): string { return a + b; } let a = foo.apply(undefined, [10]); // error: too few argumnts let b = foo.apply(undefined, [10, 20]); // error: 2nd argument is a number let c = foo.apply(undefined, [10, "hello", 30]); // error: too many arguments let d = foo.apply(undefined, [10, "hello"]); // okay! returns a string |
Mises en garde
Un inconvénient de cette nouvelle fonctionnalité est qu’en raison de certaines limitations, bind, call et apply ne peuvent pas encore totalement modéliser les fonctions génériques ou les fonctions surchargées. Lorsque vous utilisez ces méthodes sur une fonction générique, les paramètres de type sont remplacés par le type d'objet vide ({}), et lorsqu'ils sont utilisés sur une fonction avec des surcharges, seule la dernière surcharge sera modélisée.
Objet étendu sur des types génériques
JavaScript prend en charge un moyen pratique de copier les propriétés existantes d'un objet existant dans un nouvel objet qui en sera donc l’extension. Pour étendre un objet existant dans un nouvel objet, vous définissez un élément avec trois points consécutifs (...) comme suit:
| Code TypeScript : | Sélectionner tout |
1 2 3 4 5 | let person = { name: "Daniel", location: "New York City" }; // My secret revealed, I have two clones! let shallowCopyOfPerson = { ...person }; let shallowCopyOfPersonWithDifferentLocation = { ...person, location: "Seattle" }; |
TypeScript fait un très bon travail ici lorsqu'il dispose de suffisamment d'informations sur le type. Le système de typage essaie de modéliser le comportement des extensions et surcharge de nouvelles propriétés, essaie d’ignorer les méthodes, etc. Mais jusqu’à présent, il ne fonctionnait pas du tout avec les génériques.
| Code TypeScript : | Sélectionner tout |
1 2 3 4 | function merge<T, U>(x: T, y: U) { // Previously an error! return { ...x, ...y }; } |
Pour l’équipe, « C'était une erreur car nous n'avions aucun moyen d'exprimer le type de retour de merge. Il n'y avait pas de syntaxe (ni de sémantique) pouvant exprimer deux types inconnus qui allait en former un nouveau représentant leur extension.
« Nous aurions pu imaginer un nouveau concept dans le système de types appelé “type à extension d'objet”, et nous avions en fait une proposition à cet effet. Il s’agit essentiellement d’un nouvel opérateur de type qui ressemble à {... T, ... U} pour refléter la syntaxe d’un objet étendu ».
Lorsque T et U sont connus, ce type va s’écraser pour former un nouveau type d’objet.
Cependant, cela est assez complexe et nécessite l'ajout de nouvelles règles appliquées aux types pour des relations et des inférences. Alors que l’équipe a exploré plusieurs pistes, elle est récemment arrivée à deux conclusions:
- Pour la plupart des utilisations des extensions en JavaScript, ils modélisaient bien le comportement avec les types d'intersection (c'est-à-dire Foo & Bar).
- Object.assign - une fonction qui présente l’essentiel du comportement des extensions d’objets - est déjà modélisée à l’aide de types d’intersection, et...
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.
