博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
巧用 TypeScript(二)
阅读量:6001 次
发布时间:2019-06-20

本文共 6091 字,大约阅读时间需要 20 分钟。

Decorator

Decorator 早已不是什么新鲜事物。在 TypeScript 1.5 + 的版本中,我们可以利用内置类型 ClassDecoratorPropertyDecoratorMethodDecoratorParameterDecorator 更快书写 Decorator,如 MethodDecorator

declare type MethodDecorator = 
(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor
) => TypedPropertyDescriptor
| void;

使用时,只需在相应地方加上类型注解,匿名函数的参数类型也就会被自动推导出来了。

function methodDecorator (): MethodDecorator {  return (target, key, descriptor) => {    // ...  };}

值得一提的是,如果你在 Decorator 给目标类的 prototype 添加属性时,TypeScript 并不知道这些:

function testAble(): ClassDecorator {  return target => {    target.prototype.someValue = true  }}@testAble()class SomeClass {}const someClass = new SomeClass()someClass.someValue() // Error: Property 'someValue' does not exist on type 'SomeClass'.

这很常见,特别是当你想用 Decorator 来扩展一个类时。

GitHub 上有一个关于此问题的 ,直至目前,也没有一个合适的方案实现它。其主要问题在于 TypeScript 并不知道目标类是否使用了 Decorator,以及 Decorator 的名称。从这个 来看,建议的解决办法是使用 Mixin:

type Constructor
= new(...args: any[]) => T// mixin 函数的声明,还需要实现declare function mixin
(...MixIns: [Constructor
, Constructor
]): Constructor
;class MixInClass1 { mixinMethod1() {}}class MixInClass2 { mixinMethod2() {}}class Base extends mixin(MixInClass1, MixInClass2) { baseMethod() { }}const x = new Base();x.baseMethod(); // OKx.mixinMethod1(); // OKx.mixinMethod2(); // OKx.mixinMethod3(); // Error

当把大量的 JavaScript Decorator 重构为 Mixin 时,这无疑是一件让人头大的事情。

这有一些偏方,能让你顺利从 JavaScript 迁移至 TypeScript:

  • 显式赋值断言修饰符,即是在类里,明确说明某些属性存在于类上:

    function testAble(): ClassDecorator {  return target => {    target.prototype.someValue = true  }}@testAble()class SomeClass {  public someValue!: boolean;}const someClass = new SomeClass();someClass.someValue // true
  • 采用声明合并形式,单独定义一个 interface,把用 Decorator 扩展的属性的类型,放入 interface 中:

    interface SomeClass {  someValue: boolean;}function testAble(): ClassDecorator {  return target => {    target.prototype.someValue = true  }}@testAble()class SomeClass {}const someClass = new SomeClass();someClass.someValue // true

Reflect Metadata

Reflect Metadata 是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据。TypeScript 在 1.5+ 的版本已经支持它,你只需要:

  • npm i reflect-metadata --save
  • tsconfig.json 里配置 emitDecoratorMetadata 选项。

它具有诸多使用场景。

获取类型信息

譬如在 6.1 及其以下版本中,通过使用 Reflect.getMetadata API,Prop Decorator 能获取属性类型传至 Vue,简要代码如下:

function Prop(): PropertyDecorator {  return (target, key: string) => {    const type = Reflect.getMetadata('design:type', target, key);    console.log(`${key} type: ${type.name}`);    // other...  }}class SomeClass {  @Prop()  public Aprop!: string;};

运行代码可在控制台看到 Aprop type: string。除能获取属性类型外,通过 Reflect.getMetadata("design:paramtypes", target, key)Reflect.getMetadata("design:returntype", target, key) 可以分别获取函数参数类型和返回值类型。

自定义 metadataKey

除能获取类型信息外,常用于自定义 metadataKey,并在合适的时机获取它的值,示例如下:

function classDecorator(): ClassDecorator {  return target => {    // 在类上定义元数据,key 为 `classMetaData`,value 为 `a`    Reflect.defineMetadata('classMetaData', 'a', target);  }}function methodDecorator(): MethodDecorator {  return (target, key, descriptor) => {    // 在类的原型属性 'someMethod' 上定义元数据,key 为 `methodMetaData`,value 为 `b`    Reflect.defineMetadata('methodMetaData', 'b', target, key);  }}@classDecorator()class SomeClass {  @methodDecorator()  someMethod() {}};Reflect.getMetadata('classMetaData', SomeClass);                         // 'a'Reflect.getMetadata('methodMetaData', new SomeClass(), 'someMethod');    // 'b'

例子

控制反转和依赖注入

在 Angular 2+ 的版本中,便是基于此实现,现在,我们来实现一个简单版:

type Constructor
= new (...args: any[]) => T;const Injectable = (): ClassDecorator => target => {}class OtherService { a = 1}@Injectable()class TestService { constructor(public readonly otherService: OtherService) {} testMethod() { console.log(this.otherService.a); }}const Factory =
(target: Constructor
): T => { // 获取所有注入的服务 const providers = Reflect.getMetadata('design:paramtypes', target); // [OtherService] const args = providers.map((provider: Constructor) => new provider()); return new target(...args);}Factory(TestService).testMethod() // 1

Controller 与 Get 的实现

如果你在使用 TypeScript 开发 Node 应用,相信你对 ControllerGetPOST 这些 Decorator,并不陌生:

@Controller('/test')class SomeClass {  @Get('/a')  someGetMethod() {    return 'hello world';  }  @Post('/b')  somePostMethod() {}};

这些 Decorator 也是基于 Reflect Metadata 实现,不同的是,这次我们将 metadataKey 定义在 descriptorvalue 上:

const METHOD_METADATA = 'method';const PATH_METADATA = 'path';const Controller = (path: string): ClassDecorator => {  return target => {    Reflect.defineMetadata(PATH_METADATA, path, target);  }}const createMappingDecorator = (method: string) => (path: string): MethodDecorator => {  return (target, key, descriptor) => {    Reflect.defineMetadata(PATH_METADATA, path, descriptor.value);    Reflect.defineMetadata(METHOD_METADATA, method, descriptor.value);  }}const Get = createMappingDecorator('GET');const Post = createMappingDecorator('POST');

接着,创建一个函数,映射出 route

function mapRoute(instance: Object) {  const prototype = Object.getPrototypeOf(instance);    // 筛选出类的 methodName  const methodsNames = Object.getOwnPropertyNames(prototype)                              .filter(item => !isConstructor(item) && isFunction(prototype[item]));  return methodsNames.map(methodName => {    const fn = prototype[methodName];    // 取出定义的 metadata    const route = Reflect.getMetadata(PATH_METADATA, fn);    const method = Reflect.getMetadata(METHOD_METADATA, fn);    return {      route,      method,      fn,      methodName    }  })};

我们可以得到一些有用的信息:

Reflect.getMetadata(PATH_METADATA, SomeClass);  // '/test'mapRoute(new SomeClass())/** * [{ *    route: '/a', *    method: 'GET', *    fn: someGetMethod() { ... }, *    methodName: 'someGetMethod' *  },{ *    route: '/b', *    method: 'POST', *    fn: somePostMethod() { ... }, *    methodName: 'somePostMethod' * }] *  */

最后,只需把 route 相关信息绑在 express 或者 koa 上就 ok 了。

至于为什么要定义在 descriptorvalue 上,我们希望 mapRoute 函数的参数是一个实例,而非 class 本身(控制反转)。

更多

转载地址:http://fqzmx.baihongyu.com/

你可能感兴趣的文章
python项目实战:调用百度API智能识别汽车型号
查看>>
只有1%的Python 程序员搞懂过浮点数陷阱
查看>>
适配器模式(对象适配器)
查看>>
多线程的Join方法
查看>>
http协议之post请求(socket请求web内容)
查看>>
vi常用命令
查看>>
你可以知道的关于XMind的样式
查看>>
温习一下swift3 预习siwft4
查看>>
【转】document.body.offsetWidth
查看>>
107条Javascript的常用语句 - [JS]
查看>>
Hibernate Annotation多表继承映射存储与查询
查看>>
多页Excel转换成PDF时如何保存为单独文件
查看>>
freemaker操作一二 转载
查看>>
Java 9 中的 GC 调优基础
查看>>
Hudson:持续集成工具的安装、使用
查看>>
Centos 6.4下MySQL安装及配置介绍
查看>>
vncserver在centos7的使用
查看>>
EasyUI的渣性能(二)
查看>>
SQL Server高可用最高级别,加固你的DATA STORE
查看>>
pfSense book之L2TP V-P-N
查看>>