概述
面向对象的概念(java:注解,c#:特征),decorator;
angular 大量使用,react 中也会用到;
目前 JS 支持装饰器,目前处于建议征集的第二阶段;
解决的问题
-
现在要做一个事情,对下面代码中的每一个属性进行验证,按照现在的 ts 的方式,可以这么做:
class A { constructor( private prop1: string, private prop2: number, private prop3: boolean, private prop4: string, private prop5: number, private prop6: boolean, // ... 这里还有有100个属性 ) { } /** *验证函数 */ public validate() { if (!this.prop1 && this.prop1.length < 10) { // 验证prop1 } if (this.prop2 > 0) { } if (!this.prop3) { } // ... } }
-
看上面的验证代码,可以发现一些不合理的地方:
- 每一个属性都要验证,就要写一个 if 来进行判断,100 个属性就要写 100 遍,然后每一个 if 中的条件还有可能相同,就会导致写重复的代码,这是非常麻烦的;
- 写一个类的属性的时候,才是最清楚这个成员的条条框框,过了一会儿在写,可能都不记得;换一句话说是,类的成员与成员验证分开是不易于代码的维护的;
-
装饰器,能够带来额外的信息量,可以达到分离关注点的目的:
- 信息书写位置的问题
- 重复代码的问题
基于上面的这些因素,js 就提出了 装饰器的概念(现在依旧还没有正式发布),有了装饰器后,让前端的 js 类的代码更像 java 等后端语言了;但是要清楚的是,装饰器是 js 中提出的,并不是 ts 中的特殊语法;
装饰器的本质
上述两个问题产生的根源:某些信息,在定义时,能够附加的信息量有限;
装饰器的作用:为某些属性、类、参数、方法提供元数据信息(元数据:描述数据的数据);
在 JS 中,装饰器是一个函数(装饰器是要参与运行的);
装饰器可以修饰:
- 类
- 成员(属性+方法)
- 参数
类装饰器
-
类装饰器的本质是一个函数,该函数接收一个参数,表示类本身(构造函数本身);装饰器函数的运行时间:在类定义后直接运行;
-
在 TS 中,有两种方式来约束一个变量的类型是一个类:
Function
(这种方式不推荐,范围太大了);new (...args: any[])=>object
(推荐);
-
在 TS 中要使用装饰器,需要开启
experimentalDecorators: true
(实验阶段的装饰器); -
类装饰器可以具有的返回值:
- void:仅运行函数
function test(target: new (...args: any[]) => object) { console.log(target); // [Function: A] } @test class A { constructor(public prop1: string, public prop2: string) { console.log(prop1, prop2); } } new A('你好', 'ts'); // 你好 ts
- 返回一个新的类:会将新的类替换掉装饰目标
function test(str: string) { console.log(str); // 这是一个类 return function (target: new (...args: any[]) => object) { } } @test("这是一个类") class A { constructor(public prop1: string, public prop2: string) { console.log(prop1, prop2); } } new A('你好', 'ts'); // 你好 ts
- void:仅运行函数
-
多个装饰器的情况:
- 会按照后加入先调用的顺序进行调用;
type constructor = new (...args: any[]) => object; function d1() { console.log('d1') return function (target: constructor) { console.log('d1 Decorators'); } } function d2() { console.log('d2') return function (target: constructor) { console.log('d2 Decorators'); } } @d1() @d2() class A { } // 输出: // d1 // d2 // d2 Decorators // d1 Decorators
- 编译后的代码:
- 会按照后加入先调用的顺序进行调用;
成员装饰器
-
属性装饰器 也是一个函数,该函数需要两个参数:
- 参数1:如果是静态属性,则为类本身;如果是实例属性,则为类的原型;
- 参数2:固定为一个字符串,表示属性名
TypeScriptTypeScriptfunction d(target: any, key: string) { console.log(target, key); } class A { @d prop1: string; // {} prop1 @d static prop2: string; // [Function: A] prop2 }
function d() { return function d(target: any, key: string) { console.log(target, key); } } class A { @d() prop1: string; // {} prop1 @d() static prop2: string; // [Function: A] prop2 }
-
方法装饰器 也是一个函数,该函数需要三个参数:
- 参数1:如果是静态方法,则为类本身;如果是实例方法,则为类的原型;
- 参数2:固定为一个字符串,表示方法名
- 参数3:属性描述对象
function d() { return function d(target: any, key: string, descriptor: PropertyDescriptor) { console.log(target); console.log(key); console.log(descriptor); // {} // method1 // { // value: [Function: method1], // writable: true, // enumerable: false, // configurable: true // } } } class A { @d() method1() { } }
-
可以有多个装饰器修饰
// 可以被枚举 function enumerable(target: any, key: string, descriptor: PropertyDescriptor) { // console.log(target, key, descriptor); descriptor.enumerable = true; } // 废除方法,不影响之前的使用 function useless(target: any, key: string, descriptor: PropertyDescriptor) { descriptor.value = function () { console.warn(key + "方法已过期"); } } class A { @enumerable @useless method1() { console.log("method1"); } @enumerable method2() { } } const a = new A(); a.method1(); // method1方法已过期 for (const key in a) { console.log(key); // method1 // method2 }
装饰器练习
-
Descriptor.ts
export function classDescriptor(description: string) { return function (target: Function) { //保存到该类的原型中 target.prototype.$classDescription = description; } } export function propDescriptor(description: string) { return function (target: any, propName: string) { //把所有的属性信息保存到该类的原型中 if (!target.$propDescriptions) { target.$propDescriptions = []; } target.$propDescriptions.push({ propName, description }); } } export function printObj(obj: any) { //输出类的名字 if (obj.$classDescription) { console.log(obj.$classDescription); } else { console.log(Object.getPrototypeOf(obj).constructor.name); } if (!obj.$propDescriptions) { obj.$propDescriptions = []; } //输出所有的属性描述和属性值 for (const key in obj) { if (obj.hasOwnProperty(key)) { const prop = obj.$propDescriptions.find((p: any) => p.propName === key); if (prop) { console.log(`\t${prop.description}:${obj[key]}`) } else { console.log(`\t${key}:${obj[key]}`) } } } }
-
index.ts
import { classDescriptor, propDescriptor, printObj } from "./Descriptor"; @classDescriptor("文章") class Article { @propDescriptor("标题") title: string @propDescriptor("内容") content: string @propDescriptor("日期") date: Date } const ar = new Article(); ar.title = "xxxx"; ar.content = "asdfasdfasdfasdfasdf"; ar.date = new Date(); printObj(ar);
参数装饰器
-
参数装饰器:依赖注入、依赖倒置
-
要求函数有三个参数:
- 如果方法是静态的,则为类本身;如果方法是实例方法,则为类的原型
- 方法名称或者 undefined
- 在参数列表中的索引
function d(params: string) { return function (target: any, prop: string, desc: any) { console.log(target, prop, desc); } } class A { test(@d('参数1') prop1: string, @d('参数2') prop2: string) { } // {} test 1 // {} test 0 static test2(@d('参数1') prop1: string, @d('参数2') prop2: string) { } // [class A] test2 1 // [class A] test2 0 }
第三方库
reflect-metadata 库
该库的作用:保存元数据
如果安装了
reflect-metadata
,并且导入了该库,并且在某个成员上添加了元数据,并且启用了emitDecoratorMetadata: true
;则 TS 在编译结果中,会将约束的类型,作为元数据加入到相应位置;
class-validator 库
这个库是对类进行验证的,需要依赖于 reflect-metadata
class-transformer 库
把一维的对象转成类,需要依赖于 reflect-metadata
TypeScript👉 项目实战-开发俄罗斯方块
上一篇