Quand j'ai vu le mot jamais pour la première fois, j'ai pensé à quel point le type était inutile dans TypeScript. Au fil du temps, plongeant plus profondément dans les ts, j'ai commencé à comprendre à quel point ce mot est puissant. Et ce pouvoir est né de cas d'utilisation réels que j'ai l'intention de partager avec le lecteur. Peu importe, bienvenue au chat.
Ce qui n'est jamais
Si vous plongez dans l'histoire, nous verrons que le type n'est jamais apparu à l'aube de TypeScript version 2.0, avec une
description plutôt
modeste de son objectif. Si vous expliquez brièvement et librement la version des développeurs ts, le type ne sera jamais un type primitif qui représente un signe de valeurs qui ne se produiront jamais. Ou, un signe pour des fonctions qui ne renverront jamais de valeurs, soit à cause de sa boucle, par exemple une boucle infinie, soit à cause de son interruption. Et afin de montrer clairement l'essence de ce qui a été dit, je propose de voir un exemple ci-dessous:
function error(message: string): never { throw new Error(message); } function infiniteLoop(): never { while (true) { } } function infiniteRec(): never { return infiniteRec(); }
Peut-être à cause de tels exemples, j'ai eu la première impression que le type est nécessaire pour plus de clarté.
Système de type
Maintenant, je peux dire que la faune du système de type riche dans TypeScript n'est jamais due non plus. Et à l'appui de mes mots, je donnerai plusieurs types de bibliothèques de lib.es5.d.ts
type Exclude<T, U> = T extends U ? never : T; type Extract<T, U> = T extends U ? T : never; type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>; type NonNullable<T> = T extends null | undefined ? never : T; type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
De mes types avec jamais, je donnerai mon préféré - GetNames, un analogue amélioré de keyof:
type GetNames<FromType, KeepType = any, Include = true> = { [K in keyof FromType]: FromType[K] extends KeepType ? Include extends true ? K : never : Include extends true ? never : K }[keyof FromType];
Suivi des changements futurs
Lorsque la vie du projet n'est pas éphémère, le développement prend un caractère particulier. Je voudrais avoir le contrôle sur les points clés du code, avoir la garantie que vous ou les membres de votre équipe n'oublierez pas de corriger un morceau de code essentiel quand cela prend du temps. De telles sections de code sont particulièrement manifestes lorsqu'il existe un état ou une énumération de quelque chose. Par exemple, répertorier un ensemble d'actions sur une entité. Je suggère de regarder un exemple pour comprendre le contexte de ce qui se passe.
Le code ci-dessus est simplifié autant que possible uniquement afin de se concentrer sur un point important - le type AdminAction est défini dans un autre projet et il est même possible qu'il ne soit pas accompagné par votre équipe. Comme le projet va durer longtemps, il est nécessaire de protéger votre ActionEngine contre les modifications du type AdminAction à votre insu. TypeScript propose plusieurs recettes pour résoudre ce problème, dont l'une consiste à utiliser le type Never. Pour ce faire, nous devons définir un NeverError et l'utiliser dans la méthode doAction.
class NeverError extends Error {
Ajoutez maintenant une nouvelle valeur "BLOC" à AdminAction et obtenez une erreur au moment de la compilation: l'argument de type "" BLOC "" n'est pas attribuable au paramètre de type "jamais" .ts (2345).
En principe, nous y sommes parvenus. Il convient de mentionner un point intéressant: la construction du commutateur nous protège de la modification des éléments AdminAction ou de leur suppression de l'ensemble. De la pratique, je peux dire que cela fonctionne vraiment comme prévu.
Si vous ne souhaitez pas introduire la classe NeverError, vous pouvez contrôler le code en déclarant une variable de type never. Comme ça:
type AdminAction = "CREATE" | "ACTIVATE" | "BLOCK"; class ActionEngine { doAction(action: AdminAction) { switch (action) { case "CREATE":
Limite de contexte: ce + jamais
L'astuce suivante me sauve souvent des erreurs ridicules au milieu de la fatigue ou de la négligence. Dans l'exemple ci-dessous, je ne donnerai pas d'évaluation de la qualité de l'approche choisie. Avec nous, de facto, cela se produit. Supposons que vous utilisez une méthode dans une classe qui n'a pas accès aux champs de la classe. Oui, cela semble effrayant - tout cela est gov ... code.
@SomeDecorator({...}) class SomeUiPanel { @Inject private someService: SomeService; public beforeAccessHook() {
Dans un cas plus large, il peut s'agir de fonctions de rappel ou de flèche, qui ont leurs propres contextes d'exécution. Et la tâche est la suivante: comment vous protéger contre les erreurs d'exécution? Pour cela, TypeScript a la possibilité de spécifier
ce contexte.
@SomeDecorator({...}) class SomeUiPanel { @Inject private someService: SomeService; public beforeAccessHook(this: never) {
En toute honnêteté, je dirai que ce n'est jamais valable. À la place, vous pouvez utiliser void et {}. Mais c'est le type jamais qui attire l'attention lorsque vous lisez le code.
Les attentes
Invariants
Ayant une idée précise de jamais, je pensais que le code suivant devrait fonctionner:
type Maybe<T> = T | void; function invariant<Cond extends boolean>(condition: Cond, message: string): Cond extends true ? void : never { if (condition) { return; } throw new Error(message); } function f(x: Maybe<number>, c: number) { if (c > 0) { invariant(typeof x === "number", "When c is positive, x should be number"); (x + 1);
Mais hélas. L'expression (x + 1) renvoie une erreur: l'opérateur '+' ne peut pas être appliqué aux types 'Peut-être' et '1'. L'exemple lui-même que j'ai espionné dans l'article
Transfert de 30 000 lignes de code de Flow vers TypeScript.Reliure flexible
Je pensais qu'avec l'aide de Never, je pouvais contrôler les paramètres de fonction obligatoires et, dans certaines conditions, désactiver ceux qui n'étaient pas nécessaires. Mais non, cela ne fonctionnera pas:
function variants<Type extends number | string>(x: Type, c: Type extends number ? number : never): number { if (typeof x === "number") { return x + c; } return +x; } const three = variants(1, 2);
Le problème ci-dessus est résolu d'une
manière différente .
Vérification plus stricte
Je voulais que le compilateur ts ne manque pas quelque chose comme ça contrairement au bon sens.
variants(<never> {}, <never> {});
Conclusion
Au final, je veux vous proposer une petite tâche, à partir d'une série d'étranges bizarreries. Quelle ligne est l'erreur?
class never<never> { never: never; } const whats = new never<string>(); whats.never = "";
OptionDans ce dernier: le type '""' n'est pas attribuable au type 'never'.ts (2322)
C'est tout ce que je voulais dire jamais. Merci à tous pour votre attention et à bientôt.