Considérez une fonction en JavaScript appelée concat, qui prend deux types de tableau ou de tuple et les concatène ensemble comme un nouveau tableau.
Code JavaScript : | Sélectionner tout |
1 2 3 | function concat(arr1, arr2) { return [...arr1, ...arr2]; } |
Considérez également tail, qui prend un tableau ou un tuple, et renvoie tous les éléments sauf le premier.
Code JavaScript : | Sélectionner tout |
1 2 3 4 | function tail(arg) { const [_, ...result] = arg; return result } |
Comment pourrions-nous taper l'un de ces éléments dans TypeScript?
Pour concat, la seule chose valable que nous pouvions faire dans les anciennes versions du langage était d'essayer d'écrire des surcharges. Une fonction surchargée est une fonction qui est définie plusieurs fois dans une application afin d’offrir différentes logiques à l’application.
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 | function concat<>(arr1: [], arr2: []): [A]; function concat<A>(arr1: [A], arr2: []): [A]; function concat<A, B>(arr1: [A, B], arr2: []): [A, B]; function concat<A, B, C>(arr1: [A, B, C], arr2: []): [A, B, C]; function concat<A, B, C, D>(arr1: [A, B, C, D], arr2: []): [A, B, C, D]; function concat<A, B, C, D, E>(arr1: [A, B, C, D, E], arr2: []): [A, B, C, D, E]; function concat<A, B, C, D, E, F>(arr1: [A, B, C, D, E, F], arr2: []): [A, B, C, D, E, F];) |
Ce qui nous donne donc sept surcharges lorsque le deuxième tableau est toujours vide. Ajoutons-en pour quand arr2 a un argument.
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 | function concat<A2>(arr1: [], arr2: [A2]): [A2]; function concat<A1, A2>(arr1: [A1], arr2: [A2]): [A1, A2]; function concat<A1, B1, A2>(arr1: [A1, B1], arr2: [A2]): [A1, B1, A2]; function concat<A1, B1, C1, A2>(arr1: [A1, B1, C1], arr2: [A2]): [A1, B1, C1, A2]; function concat<A1, B1, C1, D1, A2>(arr1: [A1, B1, C1, D1], arr2: [A2]): [A1, B1, C1, D1, A2]; function concat<A1, B1, C1, D1, E1, A2>(arr1: [A1, B1, C1, D1, E1], arr2: [A2]): [A1, B1, C1, D1, E1, A2]; function concat<A1, B1, C1, D1, E1, F1, A2>(arr1: [A1, B1, C1, D1, E1, F1], arr2: [A2]): [A1, B1, C1, D1, E1, F1, A2]; |
Il est donc clair que cela devient déraisonnable. Malheureusement, vous vous retrouveriez également avec les mêmes types de problèmes lors de la saisie d'une fonction comme tail.
C'est un autre cas de ce que l'équipe TypeScript aime appeler « la mort par mille surcharges », et cela ne résout même pas le problème en général. Il ne donne que les types corrects pour autant de surcharges que nous voulons écrire. Si nous voulions créer un cas fourre-tout, nous aurions besoin d'une surcharge comme celle-ci:
Code TypeScript : | Sélectionner tout |
function concat<T, U>(arr1: T[], arr2, U[]): Array<T | U>;
Mais cette signature n'encode rien à propos de la longueur de l'entrée ou l'ordre des éléments lors de l'utilisation de tuples.
TypeScript 4.0 apporte deux changements fondamentaux, ainsi que des améliorations d'inférence, pour rendre leur saisie possible.
Le premier changement est que les spreads (Spread ou REST paramètre est une technique qui peut être utilisée dans le cas où nous ne connaissons pas le nombre d'arguments qui sera envoyé à la fonction, un paramètre spread est dénoté par trois points (...) qui se placent devant le nom de paramètre) dans la syntaxe de type tuple peuvent désormais être génériques. Cela signifie que nous pouvons représenter des opérations d'ordre supérieur sur des tuples et des tableaux même lorsque nous ne connaissons pas les types réels sur lesquels nous opérons. Lorsque des spreads génériques sont instanciés (ou remplacés par un type réel) dans ces types de tuples, ils peuvent produire d'autres ensembles de types de tableau et de tuple.
Par exemple, cela signifie que nous pouvons taper une fonction comme queue, sans notre problème de «mort par mille surcharges».
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | function tail<T extends any[]>(arr: readonly [any, ...T]) { const [_ignored, ...rest] = arr; return rest; } const myTuple = [1, 2, 3, 4] as const; const myArray = ["hello", "world"]; // type [2, 3, 4] const r1 = tail(myTuple); // type [2, 3, ...string[]] const r2 = tail([...myTuple, ...myArray] as const); |
Le deuxième changement est que les éléments spread peuvent apparaître n'importe où dans un tuple - pas seulement à la fin!
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 | type Strings = [string, string]; type Numbers = [number, number]; // [string, string, number, number] type StrStrNumNum = [...Strings, ...Numbers]; |
Auparavant, TypeScript générait une erreur comme celle-ci.
Envoyé par TypeScript
Lorsque nous répartissons un type sans longueur connue, le type résultant devient également illimité et tous les éléments consécutifs sont pris en compte dans le type d'élément de repos résultant.
Code : | Sélectionner tout |
1 2 3 4 5 | type Strings = [string, string]; type Numbers = number[] // [string, string, ...Array<number | boolean>] type Unbounded = [...Strings, ...Numbers, boolean]; |
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 | type Arr = readonly any[]; function concat<T extends Arr, U extends Arr>(arr1: T, arr2: U): [...T, ...U] { return [...arr1, ...arr2]; } |
Bien que cette signature soit encore un peu longue, il ne s'agit toujours que d'une signature, elle ne doit être écrite qu'une seule fois, et elle donne en fait un comportement prévisible sur tous les tableaux et tuples.
Si l'équipe est plutôt satisfaite de cette fonctionnalité, elle reconnaît qu'il existe également d'autres scénarios plus sophistiqués. Par exemple, considérons une fonction pour appliquer partiellement des arguments appelés partialCall.partialCall qui prend une fonction avec les quelques arguments initiaux attendus par cette fonction. Elle renvoie ensuite une nouvelle fonction qui prend tous les autres arguments dont la fonction a besoin et les appelle ensemble.
Code TypeScript : | Sélectionner tout |
1 2 3 | function partialCall(f, ...headArgs) { return (...tailArgs) => f(...headArgs, ...tailArgs) } |
TypeScript 4.0 améliore le processus d'inférence pour les paramètres de repos et les éléments de tuple de repos.
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 | type Arr = readonly unknown[]; function partialCall<T extends Arr, U extends Arr, R>(f: (...args: [...T, ...U]) => R, ...headArgs: T) { return (...b: U) => f(...headArgs, ...b) } |
Dans ce cas, partialCall comprend les paramètres qu'il peut et ne peut pas prendre initialement et renvoie des fonctions qui acceptent et rejettent de manière appropriée tout ce qui reste.
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | const foo = (x: string, y: number, z: boolean) => {} // This doesn't work because we're feeding in the wrong type for 'x'. const f1 = partialCall(foo, 100); // ~~~ // error! Argument of type 'number' is not assignable to parameter of type 'string'. // This doesn't work because we're passing in too many arguments. const f2 = partialCall(foo, "hello", 100, true, "oops") // ~~~~~~ // error! Expected 4 arguments, but got 5. // This works! It has the type '(y: number, z: boolean) => void' const f3 = partialCall(foo, "hello"); // What can we do with f3 now? f3(123, true); // works! f3(); // error! Expected 2 arguments, but got 0. f3(123, "hello"); // ~~~~~~~ // error! Argument of type '"hello"' is not assignable to parameter of type 'boolean'. |
Éléments de tuple étiquetés
Il est important d'améliorer l'expérience des types de tuples et des listes de paramètres. L'idée que nous pouvons utiliser des types de tuples pour les paramètres de repos devient cruciale. Par exemple, la fonction suivante qui utilise un type de tuple comme paramètre de repos…
Code TypeScript : | Sélectionner tout |
1 2 3 | function foo(...args: [string, number]): void { // ... } |
… Ne devrait pas différer de la fonction suivante…
Code TypeScript : | Sélectionner tout |
1 2 3 | function foo(arg0: string, arg1: number): void { // ... } |
… Pour tout appelant de foo.
Code TypeScript : | Sélectionner tout |
1 2 3 4 | foo("hello", 42); // works foo("hello", 42, true); // error foo("hello"); // error |
Il y a un endroit où les différences commencent à devenir observables: la lisibilité. Dans le premier exemple, nous n'avons aucun nom de paramètre pour les premier et deuxième éléments. Bien que ceux-ci n'aient aucun impact sur la vérification de type, le manque d'étiquettes sur les positions des tuples peut les rendre plus difficiles à utiliser - et il devient donc plus difficile de communiquer sur notre intention.
C’est pourquoi dans TypeScript 4.0, les types de tuples peuvent désormais fournir des étiquettes.
Code TypeScript : | Sélectionner tout |
type Range = [start: number, end: number];
Poussant davantage la connexion entre les listes de paramètres et les types de tuples, l'équipe a fait en sorte que la syntaxe des éléments de repos et des éléments facultatifs reflète celle des listes de paramètres.
Code TypeScript : | Sélectionner tout |
type Foo = [first: number, second?: string, ...rest: any[]];
Lors de l'étiquetage d'un élément de tuple, tous les autres éléments du tuple doivent également être étiquetés.
Code TypeScript : | Sélectionner tout |
1 2 3 | type Bar = [first: string, number]; // ~~~~~~ // error! Tuple members must all have names or all not have names. |
Il convient de noter que les étiquettes ne nous obligent pas à nommer nos variables différemment lors de la déstructuration. Ils sont purement là pour la documentation et l'outillage.
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 | function foo(x: [first: string, second: number]) { // ... // note: we didn't need to name these 'first' and 'second' let [a, b] = x; // ... } |
Inférence de propriété de classe des constructeurs
TypeScript 4.0 peut désormais utiliser l'analyse de flux de contrôle pour déterminer les types de propriétés dans les classes lorsque noImplicitAny est activé.
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | class Square { // Previously: implicit any! // Now: inferred to `number`! area; sideLength; constructor(sideLength: number) { this.sideLength = sideLength; this.area = sideLength ** 2; } } |
Dans les cas où tous les chemins d'un constructeur ne sont pas attribués à un membre d'instance, la propriété est considérée comme potentiellement undefined.
Code TypeScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Square { sideLength; constructor(sideLength: number) { if (Math.random()) { this.sideLength = sideLength; } } get area() { return this.sideLength ** 2; // ~~~~~~~~~~~~~~~ // error! Object is possibly 'undefined'. } } |
Source : billet TypeScript