在TypeScript中,装饰器(Decorators)是一种特殊类型的声明, 它可以附加到类声明,方法,访问器,属性或参数上,并且允许修改或注释这些声明 。装饰器在设计模式和元编程方面提供了一些强大的工具。
以下是一些常见的装饰器用法:
- 类装饰器:类装饰器应用于类声明。它接受一个参数,即类的构造函数。
- 方法装饰器:方法装饰器应用于方法声明。它接受三个参数:目标对象,方法名和方法的描述符。
- 属性装饰器:属性装饰器应用于属性声明。它接受两个参数:目标对象和属性名。
- 参数装饰器:参数装饰器应用于构造函数的参数。它接受三个参数:目标对象,方法名(如果是静态方法则是构造函数本身),和参数在参数列表中的索引。
装饰器可以用于很多场景,例如实现依赖注入、方法调用日志、验证等。
在使用装饰器时,确保你的TypeScript配置启用了experimentalDecorators
和emitDecoratorMetadata
选项。
experimentalDecorators
的作用:
- 启用装饰器的实验性支持。装饰器目前仍然是 ECMAScript 标准的一个提案,因此在 TypeScript 中,你需要将这个选项设置为 true 才能使用装饰器。
emitDecoratorMetadata
的作用:
- 在生成装饰器元数据(Decorator Metadata)方面的支持。当启用这个选项时,TypeScript 会为装饰器生成额外的元数据,以便在运行时进行反射。
装饰器的定义
-
装饰器
- 就是一个函数
- 可以注入到:类、方法、属性、参数、对象
- 扩展其功能
-
装饰器要解决的问题
- 装饰器就是解决在不修改类、属性、方法、参数的时候为其添加额外的功能。
-
高阶组件本质上也采用了装饰器的思想
- 在
Nets.js
中装饰器可以解决依赖注入的问题 - 有了依赖注入,能大大降低项目的耦合度,大大提升项目的扩展性
- 在
-
依赖注入核心思想
- 使用和创建分离
装饰器的分类
- 类装饰器
- 属性装饰器
- 方法装饰器
- 参数装饰器
- 元数据的装饰器
装饰器的两种写法
- 使用时不传参
- 装饰器工厂:使用时可以传参
环境搭建
- 安装
ts-node
- 可以使用
ts-node
命令直接运行*.ts
文件,不需要先编译成*.js
再使用node
执行
npm install -g ts-node
-
安装
concurrently
,支持合并执行,同时运行多个script
命令npm i concurrently -s
npm i nodemon -s -
新建目录,初始化TS配置,生成
tsconfig.json
tsc --init
-
生成Node包管理文件
pakcage.json
npm init
-
配置文件修改配置支持,修改
tsconfig.json
- 解开装饰器配置
- 支持普通装饰器
- 支持元数据装饰器
- 设置源码目录和编译生成JS代码目录
"experimentalDecorators": true, // 支持普通装饰器
"emitDecoratorMetadata": true, // 支持元数据装饰器
"rootDir": "./src", // 源代码根目录
"outDir": "./dist", // 生成JS代码的根目录
- 配置
package.json
- 监控
dist/teaching
目录中的js
文件,变化时执行node
命令 - 合并启动
- 命令解决
typescript
编译装饰器类时出现的bugtsconfig.json
中配置装饰器配置这是避免代码报错- 编译执行需要在指定支持装饰器选项
ts-node
命令配置- 这个配置需要根据文件名进行修改,后面会有提示
"dev:build": "tsc -w",
"dev:start": "nodemon --watch dist/decorators js --exec node ./dist/decorators/1ClassDecorator.js",
"start": "concurrently npm:dev:*",
"tsc": "tsc src/decorators/1ClassDecorator.ts -- target ES5 -w --experimentaDecorators",
"ctrl": "ts-node src/controller/HomeController.ts",
"beginapp": "nodemon --watch src/ -e ts --exec ts-node ./src/expressapp.ts"
- 创建
src/decorators
类装饰器
-
不带参数的类装饰器
-
代码
function FirstClassDecorator(targetClass: any) {
let targetClassObj = new targetClass()
targetClassObj.buy() // 下单购买
console.log("targetClass.name: ", targetClass.name) // targetClass.name: CustomerService
}
@FirstClassDecorator
class CustomerService {
name: string = "下单"
constructor() {}
buy() {
console.log(this.name + "购买")
}
placeOrder() {
console.log(this.name + "下单购买")
}
}
export {} -
package.json
配置- 当前代码的文件名
1ClassDecorator.ts
- 修改
1ClassDecorator.js
为1ClassDecorator.js
{
"name": "ts-examples",
"version": "1.0.0",
"description": "",
"scripts": {
"dev:build": "tsc -w",
"dev:start": "nodemon --watch dist/decorators js --exec node ./dist/decorators/1ClassDecorator.js",
"start": "concurrently npm:dev:*",
"tsc": "tsc src/decorators/1ClassDecorator.ts -- target ES5 -w --experimentaDecorators",
"ctrl": "ts-node src/controller/HomeController.ts",
"beginapp": "nodemon --watch src/ -e ts --exec ts-node ./src/expressapp.ts"
},
"author": "",
"license": "ISC",
"dependencies": {
"concurrently": "^8.2.2",
"nodemon": "^3.0.1"
}
} - 当前代码的文件名
-
运行代码
npm run start
-
JS源码
"use strict";
// 1. 底层JS **组合装饰器和目标类** __decorate函数
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
// argsnum 参数个数
var argsnum = arguments.length;
// targetinfo 被装饰器修饰的目标【本案例为类】
// argsnum=2 装饰器修饰的是类或者构造器参数,targetinfo=target[类名]
// argsnum=4 装饰器修饰的是方法【第四个参数desc等于null] targetinfo=该方法的数据属性【desc = Object.getOwnPropertyDescriptor(target, key) 】
// argsnum=3 装饰器修饰的是方法参数或者属性,targetinfo=undefined
var targetinfo = argsnum < 3 ? target :
desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc;
console.log("target: ", target) // target : [Function: CustomerService]
console.log("targetinfo: ", targetinfo) // targetinfo: [Function: CustomerService]
// 由于无参数,argsnum < 3, 其实是torgetinfo = target
// decorators: 保存``装饰器数组``元素
// decorators = [FirstClassDecorator]
var decorator;
// 元数据信息,支持reflect-metadata元数据
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
targetinfo = Reflect.decorate(decorators, target, key, desc);
} else
// 装饰器循环,倒着循环,说明同一个目标上有多个装饰器,执行顺序是倒着执行
for (var i = decorators.length - 1; i >= 0; i--) {
if (decorator = decorators[i]) {
// 如果参数小于3【decorator为类装饰器或者构造器参数装饰器】执行decorator(targetinfo)直接执行decorator装饰器,并传递目标targetinfo,这里是类
// 如果参数大于3【decorator为方法装饰器】 直接执行 decorator(target, key, targetinfo)
// 如果参数等于3 【decorator为方法参数装饰器或者属性装饰器】 直接执行decorator(target, key)
// targetinfo最终为各个装饰器执行后的返回值,但如果没有返回值,直接返回第S100行的targetinfo
// 这里返回一个新的targetinfo
// 是把原来的targetinfo 传入到每一个装饰器函数中
// 将CustomerService 传入到装饰器函数中,将执行逻辑交给装饰器内部执行
// 如果函数有返回值
// - 那么targetinfo就为装饰器的返回值
// - 如果装饰器函数无返回值,则执行`|| targetinfo`
targetinfo = (argsnum < 3 ? decorator(targetinfo) : argsnum > 3 ?
decorator(target, key, targetinfo) : decorator(target, key)) || targetinfo;
console.log("targetinforesult:", targetinfo)
}
}
// 参数数目小于三个argsnum < 3, 直接返回targetinfo
return argsnum > 3 && targetinfo && Object.defineProperty(target, key, targetinfo), targetinfo;
}
// 底层JS 组合装饰器和目标类 __decorate函数结束
// 不带参数的装饰器
function FirstClassDecorator(targetClass) {
var targetClassObj = new targetClass();
targetClassObj.buy(); // 下单购买
console.log("targetClass.name: ", targetClass.name); // targetClass.name: CustomerService
}
// 被装饰的类
var CustomerService = /** @class */ (function () {
function CustomerService() {
this.name = "下单";
}
CustomerService.prototype.buy = function () {
console.log(this.name + "购买");
};
CustomerService.prototype.placeOrder = function () {
console.log(this.name + "下单购买");
};
// 调用`__decorate`函数
// - 参数
// - 装饰器列表
// - 类构造函数名称 CustomerService
CustomerService = __decorate([
FirstClassDecorator,
__metadata("design:paramtypes", [])
], CustomerService);
return CustomerService;
}())
-
-
带参数的类装饰器
- 代码
function FirstClassDecorator(params: any) {
console.log("params out: ", params)
return function(targetClass: any) {
let targetClassObj = new targetClass()
targetClassObj.buy()
console.log("targetClass.name: ", targetClass.name)
console.log("params in: ", params)
}
}
@FirstClassDecorator("我是用来修饰CustomService类的装饰器参数")
class CustomerService {
name: string = "下单"
constructor() {}
buy() {
console.log(this.name + "购买")
}
placeOrder() {
console.log(this.name + "下单购买")
}
}
export {} package.json
配置- 当前代码的文件名
2ClassDecorator.ts
- 修改
1ClassDecorator.js
为2ClassDecorator.js
{
"name": "ts-examples",
"version": "1.0.0",
"description": "",
"scripts": {
"dev:build": "tsc -w",
"dev:start": "nodemon --watch dist/decorators js --exec node ./dist/decorators/2ClassDecorator.js",
"start": "concurrently npm:dev:*",
"tsc": "tsc src/decorators/1ClassDecorator.ts -- target ES5 -w --experimentaDecorators",
"ctrl": "ts-node src/controller/HomeController.ts",
"beginapp": "nodemon --watch src/ -e ts --exec ts-node ./src/expressapp.ts"
},
"author": "",
"license": "ISC",
"dependencies": {
"concurrently": "^8.2.2",
"nodemon": "^3.0.1"
}
}- 当前代码的文件名
- 运行代码
npm run start
- JS源码
"use strict";
// 1. 底层JS **组合装饰器和目标类** __decorate函数
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
// argsnum 参数个数
var argsnum = arguments.length;
// targetinfo 被装饰器修饰的目标【本案例为类】
// argsnum=2 装饰器修饰的是类或者构造器参数,targetinfo=target[类名]
// argsnum=4 装饰器修饰的是方法【第四个参数desc等于null] targetinfo=该方法的数据属性【desc = Object.getOwnPropertyDescriptor(target, key) 】
// argsnum=3 装饰器修饰的是方法参数或者属性,targetinfo=undefined
var targetinfo = argsnum < 3 ? target :
desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc;//S100
console.log("target : ", target) // [Function: CustomerService]
console.log("targetinfo: ", targetinfo) // [Function: CustomerService] // argsnum < 3 targetinfo = target
// decorators: 保存``装饰器的数组``元素
// decorators = [FirstClassDecorator]
var decorator;
// 元数据信息,支持reflect-metadata元数据
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
targetinfo = Reflect.decorate(decorators, target, key, desc);
} else
// 装饰器循环,倒着循环,说明同一个目标上有多个装饰器,执行顺序是倒着执行
for (var i = decorators.length - 1; i >= 0; i--) {
if (decorator = decorators[i]) {
// 如果参数小于3【decorator为类装饰器或者构造器参数装饰器】执行decorator(targetinfo)直接执行decorator装饰器,并传递目标targetinfo,这里是类
// 如果参数大于3【decorator为方法装饰器】 直接执行 decorator(target, key, targetinfo)
// 如果参数等于3 【decorator为方法参数装饰器或者属性装饰器】 直接执行decorator(target, key)
// targetinfo最终为各个装饰器执行后的返回值,但如果没有返回值,直接返回第S100行的targetinfo
targetinfo = (argsnum < 3 ? decorator(targetinfo) : argsnum > 3 ?
decorator(target, key, targetinfo) : decorator(target, key)) || targetinfo;
console.log("Final targetinfo result:", targetinfo)
}
}
return argsnum > 3 && targetinfo && Object.defineProperty(target, key, targetinfo), targetinfo;
}
// 带参数的装饰器
function FirstClassDecorator(params: any) {
console.log("params out: ", params)
return function(targetClass: any) {
let targetClassObj = new targetClass()
targetClassObj.buy()
console.log("targetClass.name: ", targetClass.name)
console.log("params in: ", params)
}
}
// 被装饰的类
var CustomerService = /** @class */ (function () {
function CustomerService() {
this.name = "下单";
}
CustomerService.prototype.buy = function () {
console.log(this.name + "购买");
};
CustomerService.prototype.placeOrder = function () {
console.log(this.name + "下单购买");
};
// 调用
// - 参数
// - 装饰器列表
// - 类构造函数名称 CustomerService
// 和不带参数的区别就是
// - 会把带参数装饰器先执行一遍
// - 带参数的装饰器,返回的是一个函数作为装饰器函数
// - 然后传入被装饰的类
CustomerService = __decorate([
FirstClassDecorator("我是用来修饰CustomService类的装饰器参数"),
], CustomerService);
return CustomerService;
}());
- 代码
泛型工厂继承装饰器
-
毫无相干的两个类可以相互赋值
- 条件是:属性相同
- 必须拥有我的所有属性(可以多,不能少),才能赋值给我
- 等号右边完全包含左边属性
- 父类类名,可以接受子类的类名(多态)
-
泛型工厂继承装饰器
-
装饰器类作为子类,继承父类的属性和方法
-
返回装饰器子类的类名
-
完成类的功能扩展和替换
-
代码
// 1. 完成日志信息的装饰器
function LoggerInfoDecorator<T extends { new(...args: any): any}>(targetClass: T) {
class LoggerInfoDecorator extends targetClass {
constructor(...args: any) {
super(...args)
console.log("Logging INFO targetClass: ", (targetClass as any).name)
}
}
return LoggerInfoDecorator
}
// 2. 目标类
@LoggerInfoDecorator
class Test {
name!: string
age!: number
// 1. 先执行原来的构造函数
constructor(name: string) {
this.name = name
}
eat() {
console.log(this.name, "Eating Food")
}
}
let test = new Test("Zhangsan")
test.eat()
export {} -
JS源码
// 1. 继承源码代码
let __extends = (function (Son, Parent) {
function getStaticExtendsWithForIn (Son, Parent) {
for (let key in Parent) {
if (Object.prototype.hasOwnProperty.call(Parent, key)) {
Son[key] = Parent[key]
}
}
}
function getStaticExtendsWithObjectkeys (Son, Parent) {
Object.keys(Parent).forEach((key) => {
Son[key] = Parent[key]
})
}
function getStaticExtendsWithProto (Son, Parent) {
Son.__proto__ = Parent;
}
let MyextendStatics = function (Son, Parent) {
let MyextendStatics = Object.setPrototypeOf || getStaticExtendsWithForIn ||
getStaticExtendsWithObjectkeys || getStaticExtendsWithProto
return MyextendStatics(Son, Parent)
}
return function (Son, Parent) {
MyextendStatics(Son, Parent)
function Middle () {
this.constructor = Son;
}
if (Parent) {//如果不为空 如果父类存在
Middle.prototype = Parent.prototype;
Son.prototype = new Middle()
} else {// 如果父类不存在
Son.prototype = Object.create(null)
}
console.log("Object.create(null):", Object.create(null));
}
}())
// 2. 底层JS 组合装饰器和目标类 __decorate 函数
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
// argsnum 参数个数
var argsnum = arguments.length;
// targetinfo 被装饰器修饰的目标【类或属性或方法或方法参数,本案例为类】
// argsnum=2 装饰器修饰的是类或者构造器参数,targetinfo=target[类名]
// argsnum=4 装饰器修饰的是方法【第四个参数desc等于null] targetinfo=该方法的数据属性【desc = Object.getOwnPropertyDescriptor(target, key) 】
// argsnum=3 装饰器修饰的是方法参数或者属性,targetinfo=undefined
var targetinfo = argsnum < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc;//S100
// decorator保存装饰器数组元素
var decorator;
// 元数据信息,支持reflect-metadata元数据
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
targetinfo = Reflect.decorate(decorators, target, key, desc);
} else
// 装饰器循环,倒着循环,说明同一个目标上有多个装饰器,执行顺序是倒着执行
for (var i = decorators.length - 1; i >= 0; i--) {
if (decorator = decorators[i]) {
// 如果参数小于3【decorator为类装饰器或者构造器参数装饰器】执行decorator(targetinfo)直接执行decorator装饰器,并传递目标targetinfo,这里是类
// 如果参数大于3【decorator为方法装饰器】 直接执行 decorator(target, key, targetinfo)
// 如果参数等于3 【decorator为方法参数装饰器或者属性装饰器】 直接执行decorator(target, key)
// targetinfo最终为各个装饰器执行后的返回值,但如果没有返回值,直接返回第S100行的targetinfo
targetinfo = (argsnum < 3 ? decorator(targetinfo) : argsnum > 3 ?
decorator(target, key, targetinfo) : decorator(target, key)) || targetinfo;
console.log("targetinforesult:", targetinfo)
}
}
return argsnum > 3 && targetinfo && Object.defineProperty(target, key, targetinfo), targetinfo;
}
// 底层 JS 组合装饰器和目标类 __decorate 函数结束
// 2.装饰器类
function LoggerInfoDecorator(targetClass) {
var LoggerInfoDecorator = /** @class */ (function (_super) {
__extends(LoggerInfoDecorator, _super); // ****继承关系****
function LoggerInfoDecorator() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var _this = _super.apply(this, args) || this;
console.log("Logging INFO targetClass: ", targetClass.name);
return _this;
}
return LoggerInfoDecorator;
}(targetClass));
return LoggerInfoDecorator;
}
// 2. 目标类
var Test = /** @class */ (function () {
// 1. 先执行原来的构造函数
function Test(name) {
this.name = name;
}
Test.prototype.eat = function () {
console.log(this.name, "Eating Food");
};
Test = __decorate([
LoggerInfoDecorator,
], Test);
return Test;
}());
// 4.测试
var test = new Test("ok");
-
方法装饰器
-
包含以下内容
- 方法装饰器的实现
- 方法装饰器拦截器意义
- 拦截器的前置、后置功能实现
-
不带参数方法装饰器实现
- 代码
/*
@param targetClassPrototype 类名
@param methodname : value 方法名
@param methodDecri: PropertyDescriptor 数据属性
PropertyDescriptor的定义
- 用来控制当前方法的属性
interface PropertyDescriptor {
configurable?: boolean // 是否可配置
enumerable?: boolean // 是否可枚举
value?: any // 方法名
writable?: boolean // 是否可写
get?(): any // get属性
set?(v: any): void // set属性
}
*/
function MyMethodDecorator(targetClassPrototype: any, methodname: string, methodDecri: PropertyDescriptor) {
console.log("targetClassPropertotype: ", targetClassPrototype)
console.log("key: ", methodname)
console.log("Data Attr: ", methodDecri)
methodDecri.value() // 当前被装饰的函数,数据属性中的value属性,这里就是执行被修饰的函数
}
// 目标类
class RoleService {
roleName: string = "Admin"
constructor () {}
@MyMethodDecorator
DistribRoles() {
console.log("Generating Role ...")
}
}
- 代码
-
带参数方法装饰器实现
- 代码
/*
@param targetClassPrototype 类名
@param methodname : value 方法名
@param methodDecri: PropertyDescriptor 数据属性, value 表示的是方法体
PropertyDescriptor的定义
- 用来控制当前方法的属性
interface PropertyDescriptor {
configurable?: boolean // 是否可配置
enumerable?: boolean // 是否可枚举
value?: any // 方法名
writable?: boolean // 是否可写
get?(): any // get属性
set?(v: any): void // set属性
}
*/
function MyMethodDecorator(params: any) {
return function (targetClassPrototype: any, methodname: string, methodDecri: PropertyDescriptor) {
console.log("targetClassPropertotype: ", targetClassPrototype)
console.log("key: ", methodname)
console.log("Data Attr: ", methodDecri)
console.log("Params: ", params)
methodDecri.value() // 当前被装饰的函数,数据属性中的value属性,这里就是执行被修饰的函数
}
}
// 目标类
class RoleService {
roleName: string = "Admin"
constructor () {}
@MyMethodDecorator("/Service")
DistribRoles() {
console.log("Generating Role ...")
}
}
- 代码
-
方法拦截器的意义
- 改良原有的函数,可以添加前置和后置的处理代码
属性装饰器
-
装饰器参 数
- targetClassPrototype: object
- 类名
- attrname: string | symbol
- 属性名
- targetClassPrototype: object
-
属性装饰器的应用场景
- 在属性声明时
- 进行拦截
- 添加逻辑
- 存储相关的元数据
- 在属性声明时
-
属性装饰器应用: 元数据注入
- 可以使用属性装饰器为属性附加额外的元数据
- 在某些框架中非常有用,比如Angular的
@Input()
- 用于定义一个类的属性为输入属性
class MyComponent {
@Input title: string
}
- 在某些框架中非常有用,比如Angular的
- 可以使用属性装饰器为属性附加额外的元数据
-
属性装饰器应用:验证
- 通过属性装饰器来验证类的属性值
- 可以定义一个
@IsPositive
装饰器,确保属性值是正数function IsPositive(target: any, key: string) {
const storedValue = this[key]
Object.defineProperty(target, key, {
get: function() {
return storedValue
},
set: function(value) {
if (value <= 0) {
throw new Error("Value should be a positive number")
}
storedValue = value
}
})
}
class MyClass {
@IsPositive
positiveNumber: number
}
- 可以定义一个
- 属性装饰器应用:日志与审计
- 每次属性值更改时记录日志
function LogChanges(target: any, key: string) {
let storedValue = this[key]
Object.defineProperty(target, key, {
get: function() {
return storedValue
},
set function(value) {
console.log(`Property ${key} changed from ${storedValue}` to ${value})
}
})
}
class MyClass {
@LogChanges
myProperty: string
}
- 每次属性值更改时记录日志
- 属性装饰器的其他应用场景
- 延迟加载/懒加载
- 当访问某个属性时,如果数据还没有被加载,则从后端或其他数据源动态加载
- 自动绑定
- 在React中自动绑定方法到当前实例,以 便在回调或事件处理程序中使用正确的
this
- 在React中自动绑定方法到当前实例,以 便在回调或事件处理程序中使用正确的
- 存储映射关系
- 在ORM框架中,属性装饰器可以用来定义属性与数据库列之间的映射关系
- 计算属性的缓存
- 对于计算成本高的属性,可以使用装饰器来缓存它的值,只在需要时计算
- 延迟加载/懒加载
- 通过属性装饰器来验证类的属性值
参数装饰器
-
含义
- 应用于类构造函数或方法参数的
- 它们在运行时可以接触到所属的类、方法和参数索引等信息
-
参数装饰器的应用场景
- 依赖注入
- 当类的实例被创建时,所需的依赖会自动被注入到构造函数参数中
- 元数据标记
- 在某些场景下,你可能想为某个参数标记元数据,之后这些元数据可以用于验证、转换或其他目的
- 日志和审计
- 可以用来记录或审计方法调用和参数值,特别是对于某些关键参数
- 转换或序列化
- 在方法调用之前,参数装饰器可以用来转换或序列化参数值
- 参数校验
- 参数装饰器可以与反射元数据结合使用,对方法参数进行类型校验
- 访问控制和授权
- 在某些场景下,可以使用参数装饰器检查用户权限,决定是否允许对某个方法进行访问或操作
- 关联路由和请求数据
- 在NestJS,参数装饰器可以用来提取路由参数、查询参数、请求体或请求头
- 依赖注入
访问器装饰器
-
定义
- 用于拦截类的访问器(
get
和set
)
- 用于拦截类的访问器(
-
访问器装饰器的应用场景
- 值的验证
- 在为属性赋值时(通过setter)对其进行验证
function Validate(target: any, key: string, descriptor: PropertyDescriptor) {
const originSet = descriptor.set
descriptor.set = function (value: any) {
if (!value) {
throw new Error('Invalid value')
}
originSet?.call(this, value)
}
}
class MyClass {
private _name: string
@Validate
set name(value: string) {
this._name = value
}
} - 日志
- 在获取或设置属性值时进行日志记录
function Log(target: any, key: string, descriptor: PropertyDescriptor) {
const originalGet = descriptor.get
const originalSet = descriptor.set
if (originalGet) {
descriptor.get = function () {
console.log(`Getting ${key}`)
return originalGet.call(this)
}
}
if (originalSet) {
descriptor.set = function (value: any) {
console.log(`Setting ${key} to ${value}`)
originalSet.call(this, value)
}
}
}
class MyClass {
private _value: number
@Log
get value(): number {
return this._value
}
set value(v: number) {
this._value = v
}
}
- 在获取或设置属性值时进行日志记录
- 延迟初始化/懒加载
- 在第一次访问属性时初始化它
- 计算属性的缓存
- 对于计算代价高昂的属性,可以使用访问器装饰器来缓存其值
- 自动通知
- 当属性值改变时,自动触发某些动作或事件
- 数据绑定和观察者模式
- 当属性值更改时,通知关联的UI或其他依赖项
- 在为属性赋值时(通过setter)对其进行验证
- 值的验证
元数据装饰器
-
元数据
- 为了帮助类、方法、属性实现一定的功能,而附加在其上的 一些数据
- 分类
- 自定义元数据
- 内置元数据(reflect-metadata自带)
-
初步理解
- 在定义类或类方法或对象的时候,可以设置一些元数据,我们可以获取到在类与类方法上添加元数据
- 需要引入第三方库
reflect-metadata
- 采用
@Reflect.metadata
来实现 - 元数据是指描述东西时用的数据
装饰器执行顺序
- 执行顺序
- 属性装饰器
- 方法参数装饰器
- 方法装饰器
- 类装饰器
- 装饰器的使用方式
- 前面顺序的装饰器可以保存数据
- 后面顺序的装饰器可以获取数据
装饰器总结
-
属性装饰器(Property Decorators)
- 应用于属性声明
- 不可以修改属性的描述
- 但可以使用它来存储关于属性的元数据
- 属性装饰器的表达式将在运行时作为函数调用,并带有以下两个参数
- 静态成员的类构造函数,或实例成员的类原型
- 成员名称
- 应用于属性声明
-
参数装饰器(Parameter Decorators)
- 应用于类构造函数或方法的参数
- 参数装饰器在运行时可以获取关于参数的信息
- 参数装饰器的表达式将在运行时作为函数调用,并带有以下三个参数
- 静态成员的类构造函数,或实例成员的类原型(the prototype of the class for an instance member)
- 成员名称(the name of member)
- 参数在函数参数列表(function parameter list)中的序号索引(index of the parameter)
-
方法装饰器(Method Decorators)
- 应用于方法的属性描述符
- 可用于观察、修改或替换方法定义
-
类装饰器(Class Decorators)
- 应用于类的构造函数
- 可以用来监视、修改或替换类的定义
-
访问器装饰器(Parameter Decorators)
- 应用于对象的访问器的属性描述符
- 不能同时使用在一个成员的
get
和set
访问器,只能应用于其中之一
-
案例代码
-
代码
function ClassDecorator() {
console.log('ClassDecorator: evaluated');
return function (target) {
console.log('ClassDecorator: called');
}
}
function PropertyDecorator() {
console.log('PropertyDecorator: evaluated');
return function (target, propertyKey) {
console.log('PropertyDecorator: called');
}
}
function MethodDecorator() {
console.log('MethodDecorator: evaluated');
return function (target, propertyKey, descriptor) {
console.log('MethodDecorator: called');
}
}
function ParameterDecorator() {
console.log('ParameterDecorator: evaluated');
return function (target, propertyKey, parameterIndex) {
console.log('ParameterDecorator: called');
}
}
@ClassDecorator()
class MyClass {
@PropertyDecorator()
myProperty: string;
myMethod(@ParameterDecorator() param: string) {
@MethodDecorator()
}
} -
执行结果
PropertyDecorator: evaluated
ParameterDecorator: evaluated
MethodDecorator: evaluated
ClassDecorator: evaluated
ParameterDecorator: called
MethodDecorator: called
PropertyDecorator: called
ClassDecorator: called
-