TypeScript Expression Magic

TypeScript est un langage vraiment magnifique. Son arsenal possède tout ce qui est nécessaire pour un développement de haute qualité. Et si tout à coup, quelqu'un est familier avec les croquis dramatiques sexuels avec JavaScript, alors je comprendrai. TypeScript a un certain nombre d'hypothèses, une syntaxe inattendue et des constructions délicieuses qui soulignent sa beauté, sa forme et son nouveau sens. Aujourd'hui, nous en parlons, de ces hypothèses, de la magie des expressions. Peu importe, bienvenue.

Un peu de vérité


Vrai N1.

La plupart des conceptions et des découvertes inattendues décrites ci-dessous, j'ai d'abord attiré mon attention sur les pages de Stack Overflow, github ou ont été inventées par moi-même. Et c'est seulement à ce moment-là que tout a commencé - tout cela est ici ou ici . Par conséquent, je vous demande de traiter avec compréhension à l'avance si les conclusions énoncées vous semblent banales.

Vrai N2.

La valeur pratique de certains modèles est 0.

Vrai N3.

Des exemples ont été testés sous tsc version 3.4.5 et cible es5. Juste au cas où, sous la configuration du spoiler

tsconfig.json
{
"Options de compilation": {
"OutFile": "./target/result.js",
"Module": "amd",
"Cible": "es5",
"Déclaration": vrai,
"NoImplicitAny": vrai,
"NoImplicitReturns": vrai,
"StrictNullChecks": vrai,
"StrictPropertyInitialization": vrai,
"Décors expérimentaux": vrai,
"EmitDecoratorMetadata": vrai,
"PreserveConstEnums": vrai,
"NoResolve": vrai,
"SourceMap": vrai,
"InlineSources": vrai
},
"Inclure": [
"./src"
]
}


Mise en œuvre et héritage


Rechercher : dans la section des outils, vous pouvez spécifier des interfaces, des types et des classes . Nous nous intéressons à ce dernier. Détails ici

abstract class ClassA { abstract getA(): string; } abstract class ClassB { abstract getB(): string; } // , tsc     abstract class ClassC implements ClassA, ClassB { // ^  ,   implements  . abstract getA(): string; abstract getB(): string; } 

Je pense que les développeurs TypeScript ont pris soin des «contrats stricts» exécutés via le mot-clé class. De plus, les classes n'ont pas à être abstraites.

Rechercher : les expressions sont autorisées dans la section extensions. Détails Si poser une question - s'il est possible d'hériter de 2 classes, alors la réponse formelle est non. Mais si vous parlez d'exporter des fonctionnalités - oui.

 class One { one = "__one__"; getOne(): string { return "one"; } } class Two { two = "__two__"; getTwo(): string { return "two"; } } //  ,    :   IDE (  )    . class BothTogether extends mix(One, Two) { // ^   ,    extends   info(): string { return "BothTogether: " + this.getOne() + " and " + this.getTwo() + ", one: " + this.one + ", two: " + this.two; // ^   IDE   ^  } } type FaceType<T> = { [K in keyof T]: T[K]; }; type Constructor<T> = { // prototype: T & {[key: string]: any}; new(): T; }; // TODO:    ,   .       function mix<O, T, Mix = O & T>(o: Constructor<O>, t: Constructor<T>): FaceType<Mix> & Constructor<Mix> { function MixinClass(...args: any) { o.apply(this, args); t.apply(this, args); } const ignoreNamesFilter = (name: string) => ["constructor"].indexOf(name) === -1; [o, t].forEach(baseCtor => { Object.getOwnPropertyNames(baseCtor.prototype).filter(ignoreNamesFilter).forEach(name => { MixinClass.prototype[name] = baseCtor.prototype[name]; }); }); return MixinClass as any; } const bt = new BothTogether(); window.console.log(bt.info()); // >> BothTogether: one and two, one: __one__, two: __two__ 

Trouvez : un anonymat profond et en même temps dénué de sens.

 const lass = class extends class extends class extends class extends class {} {} {} {} {}; 

Et qui écrira le mot classe avec 4 étend dans l'exemple ci-dessus?

Si oui
 // tslint:disable const Class = class Class extends class Class extends class Class extends class Class extends class Class {} {} {} {} {}; 

Et plus?

Comme ça
 // tslint:disable const lass = class Class<Class> extends class Class extends class Class extends class Class extends class Class {} {} {} {} {}; 

Eh bien, vous comprenez - c'est juste de la classe!

Point d'exclamation - Opérateur illimité et modificateur



Si vous n'utilisez pas les paramètres de compilation strictNullChecks et strictPropertyInitialization,
alors très probablement les connaissances sur le point d'exclamation sont passées près de chez vous ... En plus du but principal, 2 rôles supplémentaires lui sont assignés.

Rechercher : point d'exclamation comme opérateur d'assertion non nul

Cet opérateur vous permet d'accéder au champ de structure, qui peut être nul sans vérifier nul. Un exemple avec une explication:

  //     --strictNullChecks type OptType = { maybe?: { data: string; }; }; // ... function process(optType: OptType) { completeOptFields(optType); //   ,   completeOptFields    . window.console.log(optType.maybe!.data); // ^ -    ,    null //   !,    tsc: Object is possibly 'undefined' } function completeOptFields(optType: OptType) { if (!optType.maybe) { optType.maybe = { data: "some default info" }; } } 

Au total, cet opérateur vous permet de supprimer les vérifications inutiles de null dans le code, si nous sommes sûrs ...

Rechercher : point d'exclamation comme modificateur d'assertion d'affectation définie

Ce modificateur nous permettra d'initialiser la propriété de classe plus tard, quelque part dans le code, avec l'option de compilation strictPropertyInitialization activée. Un exemple avec une explication:

 //     --strictPropertyInitialization class Field { foo!: number; // ^ // Notice this '!' modifier. // This is the "definite assignment assertion" constructor() { this.initialize(); } initialize() { this.foo = 0; // ^   } } 

Mais tout ce mini-calcul sur le point d'exclamation n'aurait pas de sens sans un moment d'humour.

Question: Pensez-vous que l'expression suivante sera compilée?

 //     --strictNullChecks type OptType = { maybe?: { data: string; }; }; function process(optType: OptType) { if (!!!optType.maybe!!!) { window.console.log("Just for fun"); } window.console.log(optType.maybe!!!!.data); } 

La réponse
Oui

Les types



Tous ceux qui écrivent des types complexes découvrent beaucoup de choses intéressantes. J'ai donc eu de la chance.

Rechercher : un sous-type peut être référencé par le nom d'un champ du type principal.

 type Person = { id: string; name: string; address: { city: string; street: string; house: string; } }; type Address = Person["address"]; 

Lorsque vous écrivez des types vous-même, cette approche de déclaration n'a guère de sens. Mais il arrive qu'un type provienne d'une bibliothèque externe, mais pas un sous-type.

Une astuce de sous-type peut également être utilisée pour améliorer la lisibilité du code. Imaginez que vous ayez une classe de base avec un type générique dont les classes héritent. L'exemple ci-dessous illustre ce qui a été dit.

 class BaseDialog<In, Out> { show(params: In): Out {/**  .   return ... */ } } //  - class PersonDialogOld extends BaseDialog<Person[], string> {/**   */} //   class PersonDialog extends BaseDialog<Person[], Person["id"]> {/**   */} 

Trouver : à l'aide du système de type TypeScript, il est possible d'obtenir un ensemble combinatoire de types générés avec une couverture de la fonctionnalité souhaitée. Difficile à dire, je sais. J'ai longtemps pensé à cette formulation. Je vais vous montrer un exemple du modèle Builder, l'un des plus célèbres. Imaginez que vous devez construire un certain objet en utilisant ce modèle de conception.

 class SimpleBuilder { private constructor() {} static create(): SimpleBuilder { return new SimpleBuilder(); } firstName(firstName: string): this { return this; } lastName(lastName: string): this { return this; } middleName(midleName: string): this { return this; } build(): string { return "what you needs"; } } const builder = SimpleBuilder.create(); //     . const result = builder.firstName("F").lastName("L").middleName("M").build(); 

Ne regardez pas encore la méthode de création redondante, le constructeur privé, et généralement l'utilisation de ce modèle dans ts. Concentrez-vous sur la chaîne d'appel. L'idée est que les méthodes appelées doivent être utilisées strictement 1 fois. Et votre IDE devrait également en être conscient. En d'autres termes, après avoir appelé une méthode sur l'instance de générateur, cette méthode doit être exclue de la liste des méthodes disponibles. Pour obtenir une telle fonctionnalité, le type NarrowCallside nous aidera.

 type ExcludeMethod<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; type NarrowCallside<T> = { [P in keyof T]: T[P] extends (...args: any) => T ? ReturnType<T[P]> extends T ? (...args: Parameters<T[P]>) => NarrowCallside<ExcludeMethod<T, P>> : T[P] : T[P]; }; class SimpleBuilder { private constructor() {} static create(): NarrowCallside<SimpleBuilder> { return new SimpleBuilder(); } firstName(firstName: string): this { return this; } lastName(lastName: string): this { return this; } middleName(midleName: string): this { return this; } build(): string { return "what you needs"; } } const builder = SimpleBuilder.create(); const result = builder.firstName("F") // ^ -    .lastName("L") // ^ -   lastName, middleName  build .middleName("M") // ^ -   middleName  build .build(); // ^ -    build 

Rechercher : à l'aide du système de type TypeScript, vous pouvez contrôler la séquence des appels en spécifiant un ordre strict. Dans l'exemple ci-dessous, en utilisant le type DirectCallside, nous le démontrons.

 type FilterKeys<T> = ({[P in keyof T]: T[P] extends (...args: any) => any ? ReturnType<T[P]> extends never ? never : P : never })[keyof T]; type FilterMethods<T> = Pick<T, FilterKeys<T>>; type BaseDirectCallside<T, Direct extends any[]> = FilterMethods<{ [Key in keyof T]: T[Key] extends ((...args: any) => T) ? ((..._: Direct) => any) extends ((_: infer First, ..._1: infer Next) => any) ? First extends Key ? (...args: Parameters<T[Key]>) => BaseDirectCallside<T, Next> : never : never : T[Key] }>; type DirectCallside<T, P extends Array<keyof T>> = BaseDirectCallside<T, P>; class StrongBuilder { private constructor() {} static create(): DirectCallside<StrongBuilder, ["firstName", "lastName", "middleName"]> { return new StrongBuilder() as any; } firstName(firstName: string): this { return this; } lastName(lastName: string): this { return this; } middleName(midleName: string): this { return this; } build(): string { return "what you needs"; } } const sBuilder = StrongBuilder.create(); const sResult = sBuilder.firstName("F") // ^ -   firstName  build .lastName("L") // ^ -   lastName  build .middleName("M") // ^ -   middleName  build .build(); // ^ -   build 

Total



Ce sont toutes mes découvertes intéressantes sur TypeScript aujourd'hui. Merci à tous pour votre attention et à bientôt.

Source: https://habr.com/ru/post/fr465267/


All Articles