Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

TypeScript 3.8 RC apporte une nouvelle syntaxe pour l'importation/exportation de types uniquement
Ainsi que les champs privés ECMAScript

Le , par Stéphane le calme

19PARTAGES

10  0 
Imports et exports d'un type en particulier

TypeScript réutilise la syntaxe d'importation de JavaScript afin de nous permettre de référencer les types. Par exemple, dans l'exemple suivant, nous pouvons importer doThing qui est une valeur JavaScript avec Options qui est purement un type TypeScript.

Code TypeScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ./foo.ts 
interface Options { 
    // ... 
} 
  
export function doThing(options: Options) { 
    // ... 
} 
  
// ./bar.ts 
import { doThing, Options } from "./foo.js"; 
  
function doThingBetter(options: Options) { 
    // do something twice as good 
    doThing(options); 
    doThing(options); 
}

C'est pratique parce que la plupart du temps, nous n'avons pas à nous soucier de ce qui est importé, mais uniquement de nous assurer que nous importons bien quelque chose. Malheureusement, cela n'a fonctionné qu'en raison d'une fonctionnalité appelée élision d'importation. Lorsque TypeScript génère des fichiers JavaScript, il voit que Options n'est utilisé que comme type et supprime automatiquement son importation. La sortie résultante ressemble un peu à ceci:

Code TypeScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
// ./foo.js 
export function doThing(options: Options) { 
    // ... 
} 
  
// ./bar.js 
import { doThing } from "./foo.js"; 
  
function doThingBetter(options: Options) { 
    // do something twice as good 
    doThing(options); 
    doThing(options); 
}

Encore une fois, ce comportement est généralement bien, mais il provoque d'autres problèmes. Tout d’abord, il existe des endroits où il est ambigu d’exporter une valeur ou un type. Par exemple, dans l'exemple suivant, MyThing est-il une valeur ou un type ?

Code TypeScript : Sélectionner tout
1
2
3
import { MyThing } from "./some-module.js"; 
  
export { MyThing };

En nous limitant à ce fichier, il n'y a aucun moyen de le savoir. L'API transpileModule de Babel et de TypeScript émettra du code qui ne fonctionnera pas correctement si MyThing n'est qu'un type, et l'indicateur isolatedModules de TypeScript nous avertira que ce sera un problème. Le vrai problème ici est qu'il n'y a aucun moyen de dire « non non, en fait je voulais faire appel au type, ceci devrait être effacé », donc l'importation d'élision n'est pas suffisante.

L’autre problème était que l’élision d’importation de TypeScript supprimait les instructions d’importation qui ne contenaient que les importations utilisées comme types. Cela a provoqué un comportement sensiblement différent pour les modules qui ont des effets secondaires, et les utilisateurs devraient donc insérer une deuxième instruction d'importation uniquement pour contrer les effets secondaires.

Code TypeScript : Sélectionner tout
1
2
3
4
5
// This statement will get erased because of import elision. 
import { SomeTypeFoo, SomeOtherTypeBar } from "./module-with-side-effects"; 
  
// This statement always sticks around. 
import "./module-with-side-effects";

Un cas concret où l'équipe TypeScript a vu cela arriver était dans des frameworks comme Angular.js (1.x) où les services devaient être enregistrés globalement (ce qui est un effet secondaire), mais où ces services n'étaient importés que pour les types.

Code TypeScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
// ./service.ts 
export class Service { 
    // ... 
} 
register("globalServiceId", Service); 
  
// ./consumer.ts 
import { Service } from "./service.js"; 
  
inject("globalServiceId", function (service: Service) { 
    // do stuff with Service 
});

Par conséquent, ./service.js ne sera jamais exécuté et les choses vont planter lors de l'exécution.

Pour éviter ce type de problèmes, l'équipe a réalisé qu'elle devait donner aux utilisateurs un contrôle plus fin sur la façon dont les choses étaient importées/élidées.

En tant que solution dans TypeScript 3.8, l'équipe a ajouté une nouvelle syntaxe pour les importations et exportations de type uniquement.

Code TypeScript : Sélectionner tout
1
2
3
import type { SomeThing } from "./some-module.js"; 
  
export type { SomeThing };

import type importe uniquement les déclarations à utiliser pour les annotations et les déclarations de type. Il est toujours entièrement effacé, il n'y a donc pas de reste à l'exécution. De même, export type fournit uniquement une exportation qui peut être utilisée pour les contextes de type et est également effacé de la sortie de TypeScript.

Il est important de noter que les classes ont une valeur au moment de l'exécution et un type au moment du design, et l'utilisation est très sensible au contexte. Lorsque vous utilisez import type pour importer une classe, vous ne pouvez pas faire des choses comme faire une extension à partir de celle-ci.

Code TypeScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
import type { Component } from "react"; 
  
interface ButtonProps { 
    // ... 
} 
  
class Button extends Component<ButtonProps> { 
    //               ~~~~~~~~~ 
    // error! 'Component' only refers to a type, but is being used as a value here. 
  
    // ... 
}

Si vous avez déjà utilisé Flow, la syntaxe est assez similaire. Une différence est que l'équipe TypeScript a ajouté quelques restrictions pour éviter le code qui pourrait apparaître ambigu.

Code TypeScript : Sélectionner tout
1
2
3
4
5
6
// Is only 'Foo' a type? Or every declaration in the import? 
// We just give an error because it's not clear. 
  
import type Foo, { Bar, Baz } from "some-module"; 
//     ~~~~~~~~~~~~~~~~~~~~~~ 
// error! A type-only import can specify a default import or named bindings, but not both.

En conjonction avec import type, l'équipe TypeScript a également ajouté un nouvel indicateur de compilateur pour contrôler ce qui se passe avec les importations qui ne seront pas utilisées au moment de l'exécution: importsNotUsedAsValues. Ce drapeau prend 3 valeurs différentes:

remove: c'est le comportement actuel de l'abandon de ces importations. Ce sera toujours la valeur par défaut.
preserve : cette valeur préserve toutes les importations dont les valeurs ne sont jamais utilisées. Cela peut entraîner la préservation des importations/effets secondaires.
error : cette valeur préserve toutes les importations (la même que l'option preserve), mais va provoquer une erreur lorsqu'une importation de valeur n'est utilisée que comme type. Cela peut être utile si vous voulez vous assurer qu'aucune valeur n'est importée accidentellement, tout en rendant les importations d'effets secondaires explicites.


Champs privés ECMAScript

TypeScript 3.8 prend en charge les champs privés d'ECMAScript, qui font partie de la proposition de champs de classe de stade 3.

Code TypeScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person { 
    #name: string 
  
    constructor(name: string) { 
        this.#name = name; 
    } 
  
    greet() { 
        console.log(`Hello, my name is ${this.#name}!`); 
    } 
} 
  
let jeremy = new Person("Jeremy Bearimy"); 
  
jeremy.#name 
//     ~~~~~ 
// Property '#name' is not accessible outside class 'Person' 
// because it has a private identifier.

Contrairement aux propriétés régulières (même celles déclarées avec le modificateur private), les champs privés ont quelques règles à garder à l'esprit. Certaines d'entre elles sont:
  • Les champs privés commencent par un caractère #. Parfois, nous les appelons noms privés.
  • Chaque nom de champ privé a une portée unique dans sa classe conteneur.
  • Les modificateurs d'accessibilité TypeScript comme public ou private ne peuvent pas être utilisés sur des champs privés.
  • Les champs privés ne peuvent pas être accédés ou même détectés en dehors de la classe contenant.

Les déclarations de propriétés régulières sont sujettes à être écrasées dans les sous-classes :

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
class C { 
    foo = 10; 
  
    cHelper() { 
        return this.foo; 
    } 
} 
  
class D extends C { 
    foo = 20; 
  
    dHelper() { 
        return this.foo; 
    } 
} 
  
let instance = new D(); 
// 'this.foo' refers to the same property on each instance. 
console.log(instance.cHelper()); // prints '20' 
console.log(instance.dHelper()); // prints '20'

Avec les champs privés, vous n'aurez jamais à vous en préoccuper, car chaque nom de champ est unique à la classe contenante.

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
class C { 
    #foo = 10; 
  
    cHelper() { 
        return this.#foo; 
    } 
} 
  
class D extends C { 
    #foo = 20; 
  
    dHelper() { 
        return this.#foo; 
    } 
} 
  
let instance = new D(); 
// 'this.#foo' refers to a different field within each class. 
console.log(instance.cHelper()); // prints '10' 
console.log(instance.dHelper()); // prints '20'

Une autre chose à noter est que l'accès à un champ privé sur n'importe quel autre type entraînera une TypeError!

Code TypeScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Square { 
    #sideLength: number; 
  
    constructor(sideLength: number) { 
        this.#sideLength = sideLength; 
    } 
  
    equals(other: any) { 
        return this.#sideLength === other.#sideLength; 
    } 
} 
  
const a = new Square(100); 
const b = { sideLength: 100 }; 
  
// Boom! 
// TypeError: attempted to get private field on non-instance 
// This fails because 'b' is not an instance of 'Square'. 
console.log(a.equals(b));

Enfin, pour tout utilisateur de fichier .js ordinaire, les champs privés doivent toujours être déclarés avant d'être attribués.

Code TypeScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
class C { 
    // No declaration for '#foo' 
    // :( 
  
    constructor(foo: number) { 
        // SyntaxError! 
        // '#foo' needs to be declared before writing to it. 
        this.#foo = foo; 
    } 
}

JavaScript a toujours permis aux utilisateurs d'accéder aux propriétés non déclarées, tandis que TypeScript a toujours exigé des déclarations pour les propriétés de classe. Avec les champs privés, les déclarations sont toujours nécessaires, que nous travaillions dans des fichiers .js ou .ts.

Code TypeScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
class C { 
    /** @type {number} */ 
    #foo; 
  
    constructor(foo: number) { 
        // This works. 
        this.#foo = foo; 
    } 
}

Syntaxe export * as ns

Il est souvent courant d'avoir un point d'entrée unique qui expose tous les membres d'un autre module comme un seul membre.

Code TypeScript : Sélectionner tout
1
2
import * as utilities from "./utilities.js"; 
export { utilities };

C'est tellement courant qu'ECMAScript 2020 a récemment ajouté une nouvelle syntaxe pour prendre en charge ce modèle!

Code TypeScript : Sélectionner tout
export * as utilities from "./utilities.js";

Il s'agit d'une belle amélioration apportée à la qualité de JavaScript, et TypeScript 3.8 implémente cette syntaxe. Lorsque la cible de votre module est antérieure à es2020, TypeScript affichera quelque chose dans le sens du premier extrait de code.

Changements

TypeScript 3.8 contient quelques changements mineurs qui doivent être notés.

Contrôles d'assignation plus stricts aux unions avec des signatures d'index

Auparavant, les propriétés excédentaires n'étaient pas cochées lors de l'affectation à des unions où tout type avait une signature d'index - même si cette propriété excédentaire ne pouvait jamais satisfaire cette signature d'index. Dans TypeScript 3.8, le vérificateur de type est plus strict et « exempte » uniquement les propriétés des vérifications de propriétés excédentaires si cette propriété peut vraisemblablement satisfaire une signature d'index.

Code TypeScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const obj1: { [x: string]: number } | { a: number }; 
  
obj1 = { a: 5, c: 'abc' } 
//             ~ 
// Error! 
// The type '{ [x: string]: number }' no longer exempts 'c' 
// from excess property checks on '{ a: number }'. 
  
let obj2: { [x: string]: number } | { [x: number]: number }; 
  
obj2 = { a: 'abc' }; 
//       ~ 
// Error! 
// The types '{ [x: string]: number }' and '{ [x: number]: number }' no longer exempts 'a' 
// from excess property checks against '{ [x: number]: number }', 
// and it *is* sort of an excess property because 'a' isn't a numeric property name. 
// This one is more subtle.

object dans JSDoc n'est plus any sous noImplicitAny

Historiquement, la prise en charge de TypeScript pour la vérification de JavaScript a été laxiste à certains égards afin de fournir une expérience accessible.

Par exemple, les utilisateurs ont souvent utilisé Object dans JSDoc pour signifier « un objet, je ne sais pas quoi », il était alors traité comme any.

Code TypeScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
// @ts-check 
  
/** 
 * @param thing {Object} some object, i dunno what 
 */ 
function doSomething(thing) { 
    let x = thing.x; 
    let y = thing.y; 
    thing(); 
}

En fait, le traiter comme le type Object de TypeScript aboutirait à du code signalant des erreurs inintéressantes, car le type Object est un type extrêmement vague avec peu de capacités autres que des méthodes telles que toString et valueOf.

Cependant, TypeScript a un object nommé de type plus utile (notez que "o" est minuscule). Le type object est plus restrictif que Object, en ce sens qu'il rejette tous les types primitifs tels que string, boolean et number. Malheureusement, Object et object ont été traités comme any dans JSDoc.

Parce que object peut être utile et est utilisé beaucoup moins que Object dans JSDoc, l'équipe TypeScript a supprimé le comportement de cas spécial dans les fichiers JavaScript lors de l'utilisation de noImplicitAny afin que dans JSDoc, le type object se réfère vraiment au type d'objet non primitif.

Source : TypeScript

Une erreur dans cette actualité ? Signalez-le nous !