Inférence de type avec TypeScript en utilisant la construction as const et le mot clé infer

TypeScript vous permet d'automatiser de nombreuses tâches que, sans utiliser ce langage, les développeurs doivent résoudre indépendamment. Mais lorsque vous travaillez avec TypeScript, il n'est pas nécessaire d'utiliser constamment des annotations de type. Le fait est que le compilateur fait un excellent travail d'inférence de type basé sur le contexte de l'exécution du code. L'article, dont nous publions la traduction aujourd'hui, est consacré à des cas assez compliqués d'inférence de type dans lesquels le mot-clé inferet la construction sont utilisés as const.



Notions de base sur l'inférence de type


Tout d'abord, jetez un œil à l'exemple d'inférence de type le plus simple.

let variable;

Une variable déclarée de cette manière est de type any. Nous n'avons donné au compilateur aucune indication sur la façon dont nous l'utiliserons.

let variable = 'Hello!';

Ici, nous avons déclaré une variable et y avons immédiatement écrit une valeur. TypeScript peut maintenant deviner que cette variable est de type string, nous avons donc maintenant une variable typée parfaitement acceptable.

Une approche similaire s'applique aux fonctions:

function getRandomInteger(max: number) {
  return Math.floor(Math.random() * max);
}

Dans ce code, nous n'indiquons pas que la fonction getRandomIntegerrenvoie un nombre. Mais le compilateur TypeScript le sait très bien.

Inférence de type dans les génériques


Les concepts ci-dessus sont liés aux types universels (génériques). Si vous voulez en savoir plus sur les génériques, jetez un œil à ceci et à ces matériaux.

Lors de la création de types génériques, vous pouvez faire beaucoup de choses utiles. L'inférence de type facilite l'utilisation des types universels et la simplifie.

function getProperty<ObjectType, KeyType extends keyof ObjectType>(
  object: ObjectType, key: KeyType
) {
  return object[key];
}

Lorsque vous utilisez la fonction générique ci-dessus, nous n'avons pas besoin de spécifier explicitement les types.

const dog = {
  name: 'Fluffy'
};
getProperty(dog, 'name');

Cette technique, entre autres, est très utile pour créer des composants React universels. Voici le matériel à ce sujet.

Utilisation du mot clé déduire


L'une des fonctionnalités TypeScript les plus avancées qui vient à l'esprit lorsque l'on parle d'inférence de type est le mot-clé infer.

Prenons un exemple. Créez la fonction suivante:

function call<ReturnType>(
  functionToCall: (...args: any[]) => ReturnType, ...args: any[]
): ReturnType {
  return functionToCall(...args);
}

Appelez, à l'aide de cette fonction, une autre fonction, et écrivez ce qu'elle retourne dans une constante:

const randomNumber = call(getRandomInteger, 100);

L'expression précédente nous permet d'obtenir ce que la fonction getRandomIntegerqui a reçu l'entrée est retournée comme la limite supérieure de l'entier aléatoire qui lui est retourné, 100. Vrai, il y a un petit problème. Elle réside dans le fait que rien ne nous empêche d'ignorer les types d'arguments de fonction getRandomInteger.

const randomNumber = call(getRandomInteger, '100'); //   

Étant donné que TypeScript prend en charge les paramètres de répartition et de repos dans les fonctions d'ordre supérieur, nous pouvons résoudre ce problème comme ceci:

function call<ArgumentsType extends any[], ReturnType>(
  functionToCall: (...args: ArgumentsType) => ReturnType, ...args: ArgumentsType
): ReturnType {
  return functionToCall(...args);
}

Nous avons maintenant souligné que la fonction callpeut traiter un tableau d'arguments sous n'importe quelle forme, et également que les arguments doivent correspondre aux attentes de la fonction qui lui est transmise.

Maintenant, essayons à nouveau de faire un appel de fonction incorrect:

const randomNumber = call(getRandomInteger, '100');

Il en résulte un message d'erreur:

Argument of type ‘”100″‘ is not assignable to parameter of type ‘number’.

En fait, en suivant les étapes ci-dessus, nous avons simplement créé un tuple. Les tuples en TypeScript sont des tableaux de longueur fixe dont les types de valeur sont connus mais ne doivent pas nécessairement être les mêmes.

type Option = [string, boolean];
const option: Option = ['lowercase', true];

Fonctionnalité des mots clés déduire


Imaginons maintenant que notre objectif ne soit pas d'obtenir ce que renvoie la fonction, mais uniquement d'obtenir des informations sur le type de données qui lui sont renvoyées.

type FunctionReturnType<FunctionType extends (...args: any) => ?> = ?;

Le type ci-dessus n'est pas encore prêt à l'emploi. Nous devons résoudre le problème de la façon de déterminer la valeur de retour. Ici, vous pouvez tout décrire manuellement, mais cela va à l'encontre de notre objectif.

type FunctionReturnType<ReturnType, FunctionType extends (...args: any) => ReturnType> = ReturnType;
FunctionReturnType<number, typeof getRandomInteger>;

Au lieu de le faire nous-mêmes, nous pouvons demander à TypeScript de sortir le type de retour. Le mot-clé inferne peut être utilisé que dans les types conditionnels. C'est pourquoi notre code peut parfois être quelque peu désordonné.

type FunctionReturnType<FunctionType extends (args: any) => any> = FunctionType extends (...args: any) => infer ReturnType ? ReturnType : any;

Voici ce qui se passe dans ce code:

  • Il est dit de se FunctionTypedévelopper ici (args: any) => any.
  • Nous soulignons qu'il FunctionReturnTypes'agit d'un type conditionnel.
  • Nous vérifions s'il se dilate FunctionType (...args: any) => infer ReturnType.

Après avoir fait tout cela, nous pouvons extraire le type de retour de n'importe quelle fonction.

FunctionReturnType<typeof getRandomInteger>; // number

Ce qui précède est une tâche si courante que TypeScript dispose d'un utilitaire intégré ReturnType , conçu pour résoudre ce problème.

Construire en tant que const


Un autre problème lié à l'inférence de type est la différence entre les mots clés constet ceux letqui sont utilisés lors de la déclaration des constantes et des variables.

let fruit = 'Banana';
const carrot = 'Carrot';

Variable fruit- a un type string. Cela signifie que toute valeur de chaîne peut y être stockée.

Et une constante carrotest un littéral de chaîne. Il peut être considéré comme un exemple de sous-type string. La description suivante des littéraux de chaîne est donnée dans ce PR : "Le littéral de chaîne de type est un type dont la valeur attendue est une chaîne avec un contenu texte équivalent au même contenu du littéral de chaîne."

Ce comportement peut être modifié. TypeScript 3.4 introduit une nouvelle fonctionnalité intéressante appelée assertions const qui prévoit l'utilisation d'une construction as const. Voici à quoi ressemble son utilisation:

let fruit = 'Banana' as const;

Maintenant, fruitc'est un littéral de chaîne. La conception as constest également pratique lorsqu'une entité doit être rendue immuable. Considérez l'objet suivant:

const user = {
  name: 'John',
  role: 'admin'
};

En JavaScript, le mot-clé constsignifie que vous ne pouvez pas remplacer ce qui est stocké dans une constante user. Mais, d'autre part, vous pouvez changer la structure interne d'un objet enregistré dans cette constante.

Maintenant, l'objet stocke les types suivants:

const user: {
  name: string,
  role: string
};

Pour que le système perçoive cet objet comme immuable, vous pouvez utiliser la conception as const:

const user = {
  name: 'John',
  role: 'admin'
} as const;

Maintenant, les types ont changé. Les chaînes sont devenues des chaînes littérales, et non des chaînes ordinaires. Mais non seulement cela a changé. Maintenant, les propriétés sont en lecture seule:

const user: {
  readonly name: 'John',
  readonly role: 'admin'
};

Et lorsque vous travaillez avec des tableaux, des possibilités encore plus puissantes s'ouvrent à nous:

const list = ['one', 'two', 3, 4];

Le type de ce tableau est (string | number)[]. En utilisant ce tableau, as constvous pouvez le transformer en tuple:

const list = ['one', 'two', 3, 4] as const;

Maintenant, le type de ce tableau ressemble à ceci:

readonly ['one', 'two', 3, 4]

Tout cela s'applique à des structures plus complexes. Prenons l'exemple qu'Anders Halesberg a donné dans son discours au TSConf 2019 :

const colors = [
  { color: 'red', code: { rgb: [255, 0, 0], hex: '#FF0000' } },
  { color: 'green', code: { rgb: [0, 255, 0], hex: '#00FF00' } },
  { color: 'blue', code: { rgb: [0, 0, 255], hex: '#0000FF' } },
] as const;

Notre tableau est colorsdésormais protégé contre les modifications et ses éléments sont également protégés contre les modifications:

const colors: readonly [
    {
        readonly color: 'red';
        readonly code: {
            readonly rgb: readonly [255, 0, 0];
            readonly hex: '#FF0000';
        };
    },
    /// ...
]

Sommaire


Dans cet article, nous avons examiné quelques exemples d'utilisation de mécanismes avancés d'inférence de type dans TypeScript. Le mot infer- clé et le mécanisme sont utilisés ici as const. Ces outils peuvent être très utiles dans certaines situations particulièrement difficiles. Par exemple, lorsque vous devez travailler avec des entités immuables ou lorsque vous écrivez des programmes dans un style fonctionnel. Si vous souhaitez continuer à vous familiariser avec ce sujet - jetez un œil à ce matériel.

Chers lecteurs! Utilisez-vous des mots clés inferet des constructions as constdans TypeScript?


All Articles