打字稿 表达魔术

TypeScript是一门真正美丽的语言。 它的军械库拥有高质量发展所需的一切。 如果突然之间有人熟悉JavaScript的性别戏剧性素描,那么我会理解的。 TypeScript具有许多假设,意想不到的语法和令人愉悦的构造,这些构造突出了其美感,形状并充满了新的含义。 今天,我们在谈论它们,关于这些假设,关于表达的魔力。 谁在乎,欢迎。

有点道理


真实的N1。

下面概述了大多数设计和意外发现,我首先是在Stack Overflow,github或我自己发明的页面上引起了我的注意。 直到那时它才破晓-所有这些都在这里这里 。 因此,如果您认为陈述的发现对您而言不合时宜,请您事先理解。

真实的N2。

某些设计的实用价值为0。

真正的N3。

示例在tsc版本3.4.5和目标es5下进行了测试。 以防万一,在扰流板配置下

tsconfig.json
{
“ CompilerOptions”:{
“ OutFile”:“ ./target/result.js”,
“ Module”:“ amd”,
“ Target”:“ es5”,
“声明”:是,
“ NoImplicitAny”:是的,
“ NoImplicitReturns”:是,
“ StrictNullChecks”:是的,
“ StrictPropertyInitialization”:是的,
“ ExperimentalDecorators”:是的,
“ EmitDecoratorMetadata”:是的,
“ PreserveConstEnums”:是的,
“ NoResolve”:是的,
“ SourceMap”:是,
“ InlineSources”:是
},
“包含”:[
“ ./src”
]
}


实现与继承


查找 :在工具部分中,您可以指定接口,类型和 。 我们对后者感兴趣。 详情在这里

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; } 

我认为TypeScript开发人员会注意通过class关键字执行的“严格合同”。 而且,类不必是抽象的。

查找 :扩展部分中允许使用表达式。 详细资料 如果要问一个问题-是否可以从2个类中继承,那么正式答案为否。 但是,如果您要导出功能,则可以。

 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__ 

查找深刻而又毫无意义的匿名者。

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

在上面的示例中,谁将用4扩展来编写单词class?

如果是这样
 // tslint:disable const Class = class Class extends class Class extends class Class extends class Class extends class Class {} {} {} {} {}; 

还有更多?

像这样
 // tslint:disable const lass = class Class<Class> extends class Class extends class Class extends class Class extends class Class {} {} {} {} {}; 

好吧,您明白了-这只是上课!

感叹号-无限运算符和修饰符



如果您不使用strictNullChecks和strictPropertyInitialization编译设置,
那么最有可能在您附近传递了有关感叹号的知识...除了主要目的外,还为其分配了2个角色。

查找 :感叹号为非空声明运算符

此运算符允许您访问结构字段,该字段可以为null而不检查null。 带有说明的示例:

  //     --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" }; } } 

总的来说,如果我们确定...,该运算符使您可以删除代码中不必要的null检查。

查找 :感叹号作为定值断言修饰语

此修饰符将允许我们稍后在代码中的某个地方初始化strict属性,并打开strictPropertyInitialization编译选项。 带有说明的示例:

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

但是,如果没有幽默感,那么所有关于感叹号的微型计算都是没有意义的。

问题:您认为以下表达式可以编译吗?

 //     --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); } 

答案
是的

种类



编写复杂类型的每个人都会发现很多有趣的东西。 所以我很幸运。

查找 :可以通过主类型的字段名称引用子类型。

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

当您自己编写类型时,这种声明方法几乎没有道理。 但是碰巧类型来自外部库,而子类型却不是。

子类型技巧也可以用来提高代码的可读性。 假设您有一个基类,该基类具有从其继承的泛型类型。 下面的示例说明了所讲的内容。

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

查找 :在TypeScript类型系统的帮助下,可以实现生成的类型的组合集,并覆盖所需的功能。 我很难说。 我考虑了很长时间。 我将向您展示一个最著名的Builder模板示例。 想象一下,您需要使用此设计模式来构建某个对象。

 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(); 

暂时不要看一下冗余的create方法,私有构造函数,而且不要在ts中使用此模板。 专注于呼叫链。 想法是被调用的方法应严格使用1次。 您的IDE也应该意识到这一点。 换句话说,在构建器实例上调用任何方法之后,应将此方法从可用方法列表中排除。 为了实现这种功能,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 

查找 :使用TypeScript类型系统,可以通过指定严格顺序来控制调用顺序。 在下面的示例中,我们使用DirectCallside类型进行演示。

 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 

合计



这些都是我今天在TypeScript上所有有趣的发现。 谢谢大家的关注,我们很快再见。

Source: https://habr.com/ru/post/zh-CN465267/


All Articles