前端想了解后端?那得先学会 TypeScript 装饰器!TypeScript 装饰器属于实验性特性,但在Nest.js
前言
几年前学TypeScript时,装饰器属于实验性特性,因此没有关注,想等特性稳定后再看。现在开始了解Nest.js时,发现装饰器是必须掌握的内容,否则连入门都入不了(ಥ_ಥ) 。本篇文章将介绍TypeScript中装饰器的相关概念,适合准备学习Next.js的小伙伴。
1. 启用装饰器
-
新建文件夹ts
-
全局安装ts
npm install typescript -g
- 生成tsconfig.js配置文件
tsc --init
- 修改tsconfig.js配置文件,将原先被注释掉的"experimentalDecorators": true放开
{
"compilerOptions":{
"experimentalDecorators": true,
}
}
2. 装饰器概念
装饰器使用 @expression 的形式使用,expression 是一个函数,会在运行时被调用,被装饰的数据会作为参数传入到这个函数中。
- 新建index.ts
function decorator(target: any) {
target.say = function () {
console.log("hello!");
};
}
@decorator
class Animal {
static say: Function;
constructor() {}
}
Animal.say(); // hello!
代码中的 decorator 就是一个装饰器函数,接收一个 target 参数,decorator 装饰器修饰了 Animal 这个类,那么 Animal 类就被作为 target 参数传入到了 decorator 函数中。
- 让TypeScript编译器自动监听文件变化并进行编译,运行
tsc --watch
会依据index.ts文件,生成一份index.js文件。可以使用VSCode的插件Code Runner直接跑index.js文件。或者手动创建一个index.html,并将js文件引入
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./index.js"></script>
</body>
</html>
使用浏览器打开html文件,浏览器控制台打印出hello!,说明Animal类中的say方法已被赋值。
从该示例可知,装饰器类似于高阶函数,都是通过其他函数,扩展了能力。但装饰器的语法更简单,只需要在想使用的装饰器前加上@符号,装饰器就会被应用到目标上。
3. 装饰器工厂
装饰器工厂通过 @expression(args) 形式使用,装饰器工厂中的 expression 会返回一个装饰器函数,args 是用户想自定义传入的参数。
相比于普通的装饰器,装饰器工厂可传入自定义的参数,修改index.ts
function decorator(name: string) {
return function (target: any) {
target.say = function () {
console.log("My name is " + name);
};
};
}
@decorator("zhangsan")
class People1 {
static say: Function;
constructor() {}
}
People1.say(); // My name is zhangsan
@decorator("lisi")
class People2 {
static say: Function;
constructor() {}
}
People2.say(); // My name is lisi
函数decorator,它接受一个字符串 name 作为参数。decorator 函数本身返回一个内部函数,这个内部函数接收一个 target 参数,target 参数是指被装饰的类本身。
4. 多个装饰器的组合
可以对同一目标应用多个装饰器。修改index.ts
function decoratorName(name: string) {
return function (target: any) {
target.sayName = function () {
console.log("My name is " + name);
};
};
}
function decoratorAge(age: number) {
return function (target: any) {
target.sayAge = function () {
console.log("My age is " + age);
};
};
}
@decoratorName("zhangsan")
@decoratorAge(20)
class People1 {
static sayName: Function;
static sayAge: Function;
constructor() {}
}
People1.sayName(); // My name is zhangsan
People1.sayAge(); // My age is 20
这里的代码打印出的结果为:
My name is zhangsan
My age is 20
5. 装饰器类型
装饰器共有五种类型,分别为:1. 类装饰器;2.属性装饰器;3.方法装饰器;4.参数装饰器;5.访问器装饰器;
// 类装饰器
@classDecorator
class Bird {
// 属性装饰器
@propertyDecorator
name: string;
// 方法装饰器
@methodDecorator
fly(
// 参数装饰器
@parameterDecorator
meters: number
) {}
// 访问器装饰器
@accessorDecorator
get egg() {}
}
1. 类装饰器
类装饰器在类声明之前被声明,用于类、构造函数,可以用来修改或添加类的属性和方法。在装饰器概念和装饰器工厂介绍中,都使用到了类装饰器。在这里定义一个装饰器函数decorator,并显式指定返回一个类装饰器。
function decorator(param?: string): ClassDecorator {
return (target: any) => {
target.say = function () {
console.log("hello!");
};
target.run = function () {
console.log("I am running.");
};
};
}
@decorator()
class Animal {
static say: Function;
static run: Function;
constructor() {}
}
Animal.say(); // hello!
Animal.run(); // I am running.
2. 属性装饰器
属性装饰器声明在一个属性声明之前,它允许你修改类的属性或者获取关于类属性的信息。属性装饰器接收两个参数:类的原型(即类本身)和属性键名。
这里举个示例,通过属性装饰器,对属性进行拦截监听。
// 定义一个属性装饰器
function log(target: any, key: string) {
let originalValue: any;
Object.defineProperty(target, key, {
get: function() {
console.log(`Getting ${key}: ${originalValue}`);
return originalValue;
},
set: function(value) {
console.log(`Setting ${key} to ${value}`);
originalValue = value;
},
enumerable: true,
configurable: true
});
}
// 使用属性装饰器
class Person {
@log
name: string;
constructor(name: string) {
this.name = name; // 这里会触发setter
}
}
// 创建Person实例
const person = new Person("Alice");
// 访问name属性
console.log(person.name); // 这里会触发getter
person.name = "Bob"; // 这里会再次触发setter
之后想对某个属性进行监听,直接使用@log即可。
3. 方法装饰器
方法装饰器用于装饰类的方法,它们可以拦截、修改或增强方法的行为。它接收三个参数:类的原型(即类本身)、方法的名称以及方法的描述符。相比于属性装饰器,方法装饰器多了第三个参数,描述符。
这里举个示例,通过方法装饰器,将方法设置为不能改写。
function decorator(): MethodDecorator {
return (target, propertyKey, descriptor) => {
descriptor.writable = false;
};
}
class A {
@decorator()
originMethod() {
console.log("I'm Original!");
}
}
const a = new A();
try {
a.originMethod = () => {
console.log("I'm Changed!"); //Cannot assign to read only property 'originMethod'
};
} catch (e) {}
a.originMethod(); // I'm Original! 并没有被修改
4. 参数装饰器
参数装饰器用于装饰类方法的参数。主要用于元数据的注入、参数的验证或者参数的转换。它接收三个参数:类的原型、方法名以及参数在参数列表中的位置。相比于属性装饰器,参数装饰器多了第三个参数,参数在参数列表中的索引。
function decorator(params?: any): ParameterDecorator {
return (target, propertyKey, index) => {
console.log(target); //{}
console.log(propertyKey); //say
console.log(index); //1
};
}
class Animal {
say(name: string, @decorator() age?: number) {}
}
5. 访问器装饰器
访问器装饰器类似于方法装饰器,唯一的区别在于描述器中有的key不同
方法装饰器的描述器的key为:value,writable,enumerable,configurable
访问器装饰器的描述器的key为:get,set,enumerable,configurable
function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
};
}
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
//不允许删除该属性
@configurable(false)
get x() {
return this._x;
}
//不允许删除该属性
@configurable(false)
get y() {
return this._y;
}
}
相比于其他四种装饰器,访问器装饰器的使用场景较少。
结尾
參考文章:
转载自:https://juejin.cn/post/7402960438861070399