TypeScript 高级语法
1. 类的装饰器
装饰器入门
装饰器本身是一个函数,装饰器通过 @ 进行调用。
要使用装饰器,tsconfig 需要添加允许装饰器的配置:
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
尝试运行以下代码:
function testDecorator(constructor: any) {
console.log("decorator");
}
@testDecorator
class Test {}
可以看到控制台输出了 “decorator”。类装饰器在类创建好后立即执行,和是否创建类实例无关。
类装饰器接收的函数是被修饰的类的构造函数。
当使用多个装饰器时候,离 class 越近的,就优先执行。
function testDecorator(constructor: any) {
console.log("decorator");
}
function testDecorator1(constructor: any) {
console.log("decorator1");
}
@testDecorator
@testDecorator1
class Test {}
工厂模式生成装饰器
执行一个函数来生成一个新的装饰器。
function testDecorator(text: string) {
return function(constructor: any) {
console.log(text);
}
}
@testDecorator("hello")
class Test {}
使用装饰器扩展类的功能
直接放代码:
function testDecorator<T extends new (...args: any[]) => {}>(constructor: T) {
return class extends constructor {
name = "decorator";
};
}
@testDecorator
class Test {
name: string;
constructor(name: string) {
console.log(1);
this.name = name;
console.log(this.name);
console.log(2);
}
}
const test = new Test("sjh");
console.log(test);
输出结果:
1
sjh
2
Test { name: 'decorator' }
输出结果表明了,装饰器修改了类中的成员变量,同时,装饰器的执行是在类的构造器之后的。
装饰器上的泛型比较难理解,说白一点,就是指明 T 为具有构造函数的类型。下面详细解析内容:
new (...args: any[]) => {}
该构造函数函数返回一个对象类型,这个函数能接受任意多的参数,并且不限制类型。
T extends new (...args: any[]
T 这种类型可以通过这种构造函数被实例化出来。
装饰器扩展类的方法
按照上面的写法,是这样写的:
function testDecorator<T extends new (...args: any[]) => {}>(constructor: T) {
return class extends constructor {
name = "decorator";
getName() {
return this.name;
}
};
}
@testDecorator
class Test {
name: string;
constructor(name: string) {
this.name = name;
}
}
const test = new Test("sjh");
但是如果使用 @ 装饰器,TS 是无法提供拓展方法 getName 的提示的,因此得写成较为晦涩的写法:
function testDecorator<T extends new (...args: any[]) => {}>(constructor: T) {
return class extends constructor {
name = "decorator";
getName() {
return this.name;
}
};
}
const Test = testDecorator(
class {
name: string;
constructor(name: string) {
this.name = name;
}
}
);
const test = new Test("sjh");
console.log(test.getName());
@ 装饰器直接写成更原生的方法,Test 就是经过修饰后的新的类。这样的话就能被识别,就可以获得提示了。
2. 方法装饰器
同样的,方法也有装饰器。
一般有的场景是,不允许原本的方法被修改,因此可以用方法装饰器进行改装来避免这个问题。还有的场景是,在原本方法的基础上添加新的特性。
方法装饰器本质也是函数,里边有三个参数:
function decorator(target: any, key: string, descriptor: PropertyDescriptor) {
}
- target:如果是普通方法,target 对应的是类的 prototype;如果是静态方法,target 对应的是类的构造函数
- key:方法名
- descriptor:存着一些属性,用来控制该被装饰的函数
2.1 应用场景
-
类里的方法不可被修改 function uneditable(target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.writable = false;
}
class Test {
name: string;
constructor(name: string) {
this.name = name;
}
@uneditable
getName() {
return this.name;
}
}
const test = new Test("sjh");
test.getName = () => {
return "123";
};
console.log(test.getName());
-
用方法装饰器修改方法,用 descriptor.value 顶替掉原来的方法。 function changeFunc(target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.value = function () {
return "modified";
};
}
class Test {
name: string;
constructor(name: string) {
this.name = name;
}
@changeFunc
getName() {
return this.name;
}
}
const test = new Test("sjh");
console.log(test.getName());
3. 访问器的装饰器
复习一下访问器
class Test {
private _name: string;
constructor(name: string) {
this._name = name;
}
get name() {
return this._name;
}
set name(name: string) {
this._name = name;
}
}
const test = new Test("sjh");
test.name = "123";
console.log(test.name);
访问器修饰器的使用
访问器本质上还是方法,因此该咋用还是咋用。
例如,数据不可被修改:
function visitDecorator(target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.writable = false;
}
class Test {
private _name: string;
constructor(name: string) {
this._name = name;
}
get name() {
return this._name;
}
@visitDecorator
set name(name: string) {
this._name = name;
}
}
const test = new Test("sjh");
test.name = "123";
console.log(test.name);
4. 属性的装饰器
属性同样有装饰器,但是就不能接收到 descriptor 了。
第一个参数是 target,代表类的原型,第二个参数是 key,为属性名称。
自己建一个 descriptor
虽然没有 descriptor,但是可以自己写。例如,实现属性不可被改写。装饰器函数返回一个 descriptor,里边的 writable 改为 false,可以创造出不能被修改的属性。
function nameDecorator(target: any, key: string): any {
const descriptor: PropertyDescriptor = {
writable: false,
};
return descriptor;
}
class Test {
@nameDecorator
name = "sjh";
}
const test = new Test();
test.name = "123";
console.log(test.name);
target 指的是类的原型
function nameDecorator(target: any, key: string): any {
target[key] = "123";
}
class Test {
@nameDecorator
name = "sjh";
}
const test = new Test();
console.log(test.name);
上面的代码输出结果仍然是 “sjh”,原因在于,“sjh” 是在实例下的属性,而装饰器里的 “123” 被放置在了原型上。根据原型链的查找原则,优先找到实例下的属性。
如果要访问 “123”,则需要找实例的隐式原型上的 name 属性。
console.log((test as any).__proto__.name)
5. 参数装饰器
可以对类里的方法的参数进行装饰。
装饰器携带三个参数:
- target:类的原型
- method:方法名
- paramIndex:参数在方法里的 index
function paramDecorator(target: any, key: string, paramIndex: number): any {
console.log(target, key, paramIndex);
}
class Test {
getInfo(name: string, @paramDecorator age: number) {
console.log(name, age);
}
}
const test = new Test();
console.log(test.getInfo("sjh", 18));
6. 装饰器的实际使用范例
获取对象里的属性,但是这个属性不一定存在,不存在的话给提示,普通写法会这样写:
class Test {
userInfo: any = undefined;
getName() {
try {
return this.userInfo.name;
} catch (e) {
console.log("userInfo.name 不存在")
}
}
getAge() {
try {
return this.userInfo.age;
} catch (e) {
console.log("userInfo.age 不存在")
}
}
}
但是这样会有大量的重复代码,因此这里使用装饰器解决。
function catchError(msg: string) {
return function(target: any, key: string, descriptor: PropertyDescriptor) {
const fn = descriptor.value;
descriptor.value = function () {
try {
fn();
} catch (e) {
console.log(msg);
}
};
}
}
class Test {
userInfo: any = undefined;
@catchError("userInfo.name 不存在")
getName() {
return this.userInfo.name;
}
@catchError("userInfo.age 不存在")
getAge() {
return this.userInfo.age;
}
}
7. reflect-metadata
元数据是挂在对象上的数据,但是不能直接通过输出查看到。
yarn add reflect-metadata
添加和获取元数据内容基本使用
import "reflect-metadata";
const user = {
name: "sjh",
};
Reflect.defineMetadata("data", "test", user);
console.log(Reflect.getMetadata("data", user));
用装饰器添加和获取元数据
元数据可以添加到类、类方法、类属性上。
import "reflect-metadata";
@Reflect.metadata("data", "test")
class User {
@Reflect.metadata("nameMeta", "hhh")
name = "sjh";
}
console.log(Reflect.getMetadata("data", User));
console.log(Reflect.getMetadata("nameMeta", User.prototype, "name"));
其他 API
hasMetadata
hasOwnMetadata
getMetadataKeys
deleteMetadata
@Reflect.metadata 实现原理
本质上是一个函数,返回一个装饰器。
自己实现一个功能相同的注解:
function setData(dataKey: string, msg: string) {
return function (target: User, key: string) {
Reflect.defineMetadata(dataKey, msg, target, key);
};
}
@showData
class User {
@Reflect.metadata("data", "name")
getName() {
console.log("name");
}
@setData("data", "age")
getAge() {
console.log("age");
}
}
8. 装饰器执行顺序
方法装饰器优先于类装饰器
|