TypeScript ist eine wirklich schöne Sprache. Das Arsenal verfügt über alles, was für eine qualitativ hochwertige Entwicklung notwendig ist. Und wenn plötzlich jemand mit sexdramatischen Skizzen mit JavaScript vertraut ist, dann werde ich verstehen. TypeScript hat eine Reihe von Annahmen, unerwartete Syntax und reizvolle Konstruktionen, die seine Schönheit, Form und Füllung mit neuer Bedeutung betonen. Heute sprechen wir über sie, über diese Annahmen, über die Magie der Ausdrücke. Wen kümmert es, willkommen.
Ein bisschen Wahrheit
Richtig N1.
Die meisten der unten beschriebenen Designs und unerwarteten Funde haben mich zuerst auf die Seiten von Stack Overflow, Github aufmerksam gemacht oder wurden von mir selbst erfunden. Und erst dann dämmerte es - all das ist
hier oder
hier . Deshalb bitte ich Sie, im Voraus verständnisvoll zu behandeln, wenn Ihnen die angegebenen Ergebnisse banal erscheinen.
Richtig N2.
Der praktische Wert einiger Designs ist 0.
Richtig N3.
Beispiele wurden unter tsc Version 3.4.5 und Ziel es5 getestet. Nur für den Fall, unter der Spoiler-Konfiguration
tsconfig.json{
"CompilerOptions": {
"OutFile": "./target/result.js",
"Modul": "amd",
"Ziel": "es5",
"Erklärung": wahr,
"NoImplicitAny": wahr,
"NoImplicitReturns": true,
"StrictNullChecks": wahr,
"StrictPropertyInitialization": wahr,
"ExperimentalDecorators": wahr,
"EmitDecoratorMetadata": wahr,
"PreserveConstEnums": wahr,
"NoResolve": wahr,
"SourceMap": wahr,
"InlineSources": true
},
"Einschließen": [
"./src"
]]
}}
Implementierung und Vererbung
Suchen : Im Abschnitt "Geräte" können Sie Schnittstellen, Typen und
Klassen angeben. Letzteres interessiert uns. Details
hierabstract 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; }
Ich denke, TypeScript-Entwickler haben sich um die 'strengen Verträge' gekümmert, die über das Schlüsselwort class ausgeführt wurden. Darüber hinaus müssen Klassen nicht abstrakt sein.
Suchen : Ausdrücke sind im erweiterten Bereich zulässig.
Details Wenn Sie eine Frage stellen möchten - ob es möglich ist, von 2 Klassen zu erben, lautet die formale Antwort Nein. Aber wenn Sie meinen, Funktionen zu exportieren - ja.
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__
Finden : eine
tiefe und gleichzeitig bedeutungslose Anonymität.
const lass = class extends class extends class extends class extends class {} {} {} {} {};
Und wer schreibt die Wortklasse mit 4 erweitert im obigen Beispiel?
Wenn ja // tslint:disable const Class = class Class extends class Class extends class Class extends class Class extends class Class {} {} {} {} {};
Und mehr?
So // tslint:disable const lass = class Class<Class> extends class Class extends class Class extends class Class extends class Class {} {} {} {} {};
Nun, du verstehst es - es ist nur Klasse!
Ausrufezeichen - Unbegrenzter Operator und Modifikator
Wenn Sie die Kompilierungseinstellungen strictNullChecks und strictPropertyInitialization nicht verwenden,
dann ist höchstwahrscheinlich das Wissen über das Ausrufezeichen in Ihrer Nähe vergangen ... Zusätzlich zum Hauptzweck werden ihm 2 weitere Rollen zugewiesen.
Suchen : Ausrufezeichen als
Nicht-Null-AssertionsoperatorMit diesem Operator können Sie auf das Strukturfeld zugreifen, das null sein kann, ohne nach null zu suchen. Ein Beispiel mit einer Erklärung:
// --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" }; } }
Insgesamt können Sie mit diesem Operator unnötige Überprüfungen auf Null im Code entfernen, wenn wir sicher sind, dass ...
Suchen : Ausrufezeichen als
Modifikator für die Zusicherung einer bestimmten ZuweisungMit diesem Modifikator können wir die Klasseneigenschaft später irgendwo im Code mit aktivierter Kompilierungsoption strictPropertyInitialization initialisieren. Ein Beispiel mit einer Erklärung:
// --strictPropertyInitialization class Field { foo!: number; // ^ // Notice this '!' modifier. // This is the "definite assignment assertion" constructor() { this.initialize(); } initialize() { this.foo = 0; // ^ } }
Aber all diese Mini-Berechnungen über das Ausrufezeichen wären ohne einen Moment Humor nicht sinnvoll.
Frage: Glauben Sie, dass der folgende Ausdruck kompiliert wird?
// --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); }
Typen
Jeder, der komplexe Typen schreibt, entdeckt viele interessante Dinge. Also hatte ich Glück.
Suchen : Ein Subtyp kann durch den Namen eines Feldes des Haupttyps referenziert werden.
type Person = { id: string; name: string; address: { city: string; street: string; house: string; } }; type Address = Person["address"];
Wenn Sie selbst Typen schreiben, ist dieser Deklarationsansatz kaum sinnvoll. Es kommt jedoch vor, dass ein Typ aus einer externen Bibliothek stammt, ein Subtyp jedoch nicht.
Ein Subtyp-Trick kann auch verwendet werden, um die Lesbarkeit des Codes zu verbessern. Stellen Sie sich vor, Sie haben eine Basisklasse mit einem generischen Typ, von dem die Klassen erben. Das folgende Beispiel zeigt, was gesagt wurde.
class BaseDialog<In, Out> { show(params: In): Out {/** . return ... */ } } // - class PersonDialogOld extends BaseDialog<Person[], string> {/** */} // class PersonDialog extends BaseDialog<Person[], Person["id"]> {/** */}
Suchen : Mit Hilfe des TypeScript-Typsystems ist es möglich, einen kombinatorischen Satz generierter Typen mit Abdeckung der gewünschten Funktionalität zu erzielen. Schwer zu sagen, ich weiß. Ich habe lange über diese Formulierung nachgedacht. Ich werde Ihnen ein Beispiel der Builder-Vorlage als eine der bekanntesten zeigen. Stellen Sie sich vor, Sie müssen ein bestimmtes Objekt mit diesem Entwurfsmuster erstellen.
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();
Sehen Sie sich noch nicht die redundante Erstellungsmethode, den privaten Konstruktor, und im Allgemeinen die Verwendung dieser Vorlage in ts an. Konzentrieren Sie sich auf die Anrufkette. Die Idee ist, dass aufgerufene Methoden nur einmal verwendet werden sollten. Und Ihre IDE sollte sich dessen auch bewusst sein. Mit anderen Worten, nach dem Aufrufen einer Methode in der Builder-Instanz sollte diese Methode aus der Liste der verfügbaren Methoden ausgeschlossen werden. Um diese Funktionalität zu erreichen, hilft uns der Typ NarrowCallside.
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
Suchen : Mit dem TypeScript-Typsystem können Sie die Reihenfolge der Aufrufe steuern, indem Sie eine strikte Reihenfolge angeben. Im folgenden Beispiel am Beispiel des DirectCallside-Typs wird dies demonstriert.
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
Insgesamt
Dies sind alle meine interessanten Erkenntnisse zu TypeScript heute. Vielen Dank für Ihre Aufmerksamkeit und bis bald.