Les string enums

Réclamées depuis un bout de temps, elles arrivent enfin dans cette version 2.4 : les string enums ! On les déclare de la façon suivante :

1
2
3
4
5
6
7

enum Size {
Mo = "megaoctet",
Go = "gigaoctet",
To = "teraoctet"
}

À noter qu’il est possible de mixer dans une enums des membres numériques et des membres initialisée par une chaîne. Néanmoins ces derniers possèdent quelques subtilités :

  • obligatoirement initialisée (pas initialisé par défaut comme les membres numériques)
  • pas de reverse mapping

On peut souligner ce dernier point en observant le code généré :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

// TypeScript
enum Mixed {
Nombre,
Chaine = "coucou"
}

// Code Javascript généré
var Mixed;
(function (Mixed) {
Mixed[Mixed["Nombre"] = 0] = "Nombre";
Mixed["Chaine"] = "coucou";
})(Mixed || (Mixed = {}));

// Conclusion (reverse mapping)
Mixed[0] // "Nombre"
Mixed['coucou'] // undefined

Mis à part ça, on les utilise comme les membres numériques. Le fait de déclarer une const enum à excatement le même impact que sur les membres numériques.

Weak Type

Nouveau concept qui vient renforcer le type checking lors de l’utilisation d’interface d’objet contenant uniquement des propriétés optionnelles. Un type est consideré comme un Weak Type s’il remplie ces trois conditions :

  1. type object contenant au moins une propriétés
  2. toutes les propriétés sont optionnelles
  3. le type n’a pas de string index signature, number index signature, call signature ou construct signature.

Voici un exemple de Weak Type :

1
2
3
4
5
6

interface Foo {
prop1?: string;
prop2?: number;
}

Avant TypeScript 2.4, le compilateur acceptait que n’importe quel objet match cette interface, du moment que c’était un objet…. Beaucoup d’erreurs potentielles n’étaient donc pas détectées. Désormais le compilateur accepte un objet si et seulement si l’une de ses propriétés correspond à l’une des propriétés du Weak Type.

Exemple :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

interface Options {
data?: string;
timeout?: number;
maxRetries?: number;
}

function sendMessage(options: Options) {
// ...
}

const opts = {
header: "hello world!",
retryOnFail: true,
}

const opts2 = {
timeout: 10,
retryOnFail: true,
}

sendMessage(opts); // ERROR -> Type '{ header: string; retryOnFail: boolean; }' has no properties in common with type 'Options'.
sendMessage(opts2); // OK

Import dynamique

Pour rappel les imports dynamiques sont une proposition en cours d’étude par le TC39 pour être intégré dans les spécifications de l’ECMAScript. Actuellement à l’étape 3 de validation (dernière étape avant parution), cette proposition devrait faire partie de la version 2019 mais est déjà supportée par certains outils comme Webpack par exemple. Pour citer quelques cas d’utilisation :

  • importer contionellement des modules
  • charger des modules dont le nom est calculé
  • charger du code à la demande

Bon mais que fait TypeScript dans tous ça ? Eh bien, Il supporte la syntaxe lorsque vous précisez l’option "module":esnext dans votre tsconfig.json, voici un petit exemple fait sur Stackblitz

Stackblitz on demand module example

Covariant checking pour les callback

Premièrement un petit rappel sur la covariance/contravariance/invariance/bivariance.

Attention : ceci n’est pas du code TypeScript

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
28
29

class Animals {}
class Dog extends Animals {}
class Bulldog extends Dog {}

// Invariance : accepte ni les sous-classes ni les classes mère
function method(value: Invariant<Dog>) {...}
method(new Animals()); // ERROR
method(new Dog()); // OK
method(new Bulldog()); // ERROR

// Covariance : accepte les sous-classes
function method(value: Covariant<Dog>) {...}
method(new Animals()); // ERROR
method(new Dog()); // OK
method(new Bulldog()); // OK

// Contravariance : accepte les classe mère
function method(value: Contravariant<Dog>) {...}
method(new Animals()); // OK
method(new Dog()); // OK
method(new Bulldog()); // ERROR

// Bivariance : accepte les sous-classes et les classes mères
function method(value: Bivariant<Dog>) {...}
method(new Animals()); // OK
method(new Dog()); // OK
method(new Bulldog()); // OK

Étant donné qu’avec TypeScript il n’est pas possible de préciser la covariance ou contravariance des paramètres d’une fonction ils sont par défaut bivariance (explication de la FAQ de TypeScript). Mais dans TypeScript 2.4, les paramètres de callback sont désormais checker via la covariance. Cela améliore grandement la détection de potentielles erreurs, par exemple :

1
2
3
4
5
6
7
8
9
10
11

// Avant TypeScript 2.4
const a: Promise<{ foo: 'bar' }> = Promise.resolve({ foo: 'bar' }); // OK
const b: Promise<{ foo: 'bar' }> = Promise.resolve({ foo: 'typo' }); // OK
const c: Promise<{ foo: 'bar' }> = Promise.resolve({}); // OK

// Après TypeScript 2.4
const a: Promise<{ foo: 'bar' }> = Promise.resolve({ foo: 'bar' }); // OK
const b: Promise<{ foo: 'bar' }> = Promise.resolve({ foo: 'typo' }); // ERROR
const c: Promise<{ foo: 'bar' }> = Promise.resolve({}); // ERROR

Si vous souhaitez en apprendre plus sur le sujet vous trouverer le thread github ici.

Dans cette nouvelle version de TypeScript, un gros effort à égalemet été fait sur le check des types génériques ! Je vous invite donc à jeter un coup d’oeil à la roadmap.

À bientôt.