外观
06.装饰器
5366字约18分钟
2024-05-31
理解装饰器
装饰器(Decorator)其实是面向对象中的概念,在一些纯粹的面向对象的类型语言中早就有装饰器的内容了,在java中叫注解,在C#中叫特征。装饰器并不是 Typescript 新引出的概念,是 JavaScript 本身就支持的内容。而且,出来的还特别早,在 ES6 的时候,就已经提出了装饰器。只不过,将近 10 年过去了,装饰器的规范几乎从头开始重写了好几次,但它还没有成为规范的一部分。到现在,2024 年,也才刚刚进展到第 3 阶段不久
前些年随着面向对象语言的流行,JavaScript 的装饰器也一直备受期待,不过由于 JavaScript 不是仅仅局限于基于浏览器的应用程序,规范的制定者必须考虑到可以执行 JavaScript 的各种平台上 javascript 上执行的情况,规范也迟迟未定下来。
不过世事变迁,现在纯前端的框架也来到了 react18,vue3 的时代,这两个框架都倾向于使用更加模块化和函数式的编程风格。这种风格更有利于实现摇树优化(Tree Shaking),这是现代前端构建工具(如 Webpack、Rollup)中的一个关键特性
不过 Angular 就一直在广泛使用装饰器,还有 nodejs 流行的后端框架 NestJS 对装饰器也有很好的支持
无论怎么样,装饰器理论是很优秀的,对于我们对整个程序设计的理解是有帮助的。
装饰器模式
其实在程序设计中,一直有装饰器模式,它一种结构设计模式,通过将对象置于包含行为的特殊包装器对象中,可以将新的行为附加到对象上
// 组件接口
class TextMessage {
constructor(message) {
this.message = message;
}
getText() {
return this.message;
}
}
// 装饰器基类
class MessageDecorator {
constructor(textMessage) {
this.textMessage = textMessage;
}
getText() {
return this.textMessage.getText();
}
}
// 具体装饰器
class HTMLDecorator extends MessageDecorator {
getText() {
const msg = super.getText();
return `<p>${msg}</p>`;
}
}
class EncryptDecorator extends MessageDecorator {
getText() {
const msg = super.getText();
// 加密逻辑
return this.encrypt(msg);
}
encrypt(msg) {
return msg.split('').reverse().join('');
}
}
// 使用
let message = new TextMessage('Hello World');
message = new HTMLDecorator(message);
message = new EncryptDecorator(message);
console.log(message.getText()); // 输出加密的 HTML 格式文本
这是面向对象的写法,其实在 js 中,我们也能写成函数式的,因为上面的写法,我们完全可以使用高阶函数替代
// 基础消息类
class TextMessage {
constructor(message) {
this.message = message;
}
getText() {
return this.message;
}
}
// 高阶函数 - HTML装饰器
function HtmlDecoratedClass(BaseClass) {
return class extends BaseClass {
getText() {
const originalText = super.getText();
return `<p>${originalText}</p>`;
}
};
}
// 高阶函数 - 加密装饰器
function EncryptDecoratedClass(BaseClass) {
return class extends BaseClass {
getText() {
const originalText = super.getText();
// 这里应该是你的加密逻辑
return this.encrypt(originalText);
}
encrypt(msg) {
// 简单处理加密
return msg.split('').reverse().join('');
}
};
}
// 使用装饰器
let DecoratedClass = HtmlDecoratedClass(TextMessage);
DecoratedClass = EncryptDecoratedClass(DecoratedClass);
const messageInstance = new DecoratedClass('Hello World');
console.log(messageInstance.getText()); // 输出被 HTML 格式化并加密的文本
装饰器的作用
这样很简单的实现了装饰器的设计模式,但是这样的代码实际上在工作中还是有一些问题,比如我们创建一个用户,然后可能在后期,我们需要对用户中的数据进行验证:
class User {
// 注意:严格检查(strict)不赋初始值会报错
// 演示可以设置 strictPropertyInitialization: false
loginId: string; // 必须是3-5个字符
loginPwd: string; // 必须是6-12个字符
age: number; // 必须是0-100之间的数字
gender: '男' | '女';
}
const u = new User();
// 对用户对象的数据进行验证
function validateUser(user: User) {
// 对账号进行验证
// 对密码进行验证
// 对年龄进行验证
// ...
}
这实际上,是要求对类的属性都需要进行处理,是不是就要进行装饰。我们下面的validateUser
这个函数,实际就在处理这个问题。这咋一看没有什么问题,但是,其实应该在我们写类,写属性的时候,对这个属性应该怎么处理才是最了解的。而不是当需要验证的时候,再写函数进行处理。
当然,你可能会说,把validateUser
这个函数写到类中去不就行了?
class User {
// 注意:严格检查(strict)不赋初始值会报错
// 演示可以设置 strictPropertyInitialization: false
loginId: string; // 必须是3-5个字符
loginPwd: string; // 必须是6-12个字符
age: number; // 必须是0-100之间的数字
gender: '男' | '女';
validate() {
// 对账号进行验证
// 对密码进行验证
// 对年龄进行验证
// ...
}
}
可以,但是并没有解决我们提出的问题:当我们写这个类的属性的时候,对这个属性应该是最了解的。如果我们能在写属性的时候,就直接可以定义这些验证是最舒服的。
还有一个问题,现在仅仅是 User 这个类,那么我们还有其他的类需要验证,也是差不多的验证长度啊,是不是必须得啊,对这个属性的描述是什么啊,这些都需要。那我们肯定在另外一个类中,还需要来上validate
函数这一套。能不能有一种语法机制,帮我们处理这个问题呢?
装饰器就可以帮我们解决这些问题
1、关注点分离:写属性,然后再写函数处理,其实就分离了我们的关注点
2、代码重复:不同的类可能只是属性不一样,但是可能需要验证,分析或者处理的内容实际上差不多
伪代码
class User {
@required
@range(3, 5)
@description("账号")
loginId: string; // 中文描述:账号,验证:1.必填 2.必须是3-5个字符
......
}
这两个问题产生的根源其实就是我们在定义某些信息的时候,能够附加的信息有限,如果能给这些信息装饰一下,添加上有用的信息,就能处理了,这就是装饰器
所以装饰器的作用:为某些属性、类、方法、参数提供元数据信息(metadata)
又来一个名词:
元数据:描述数据的数据,上面的伪代码中,这三个装饰器@required
@range(3, 5)
@description("账号")
其实就是用来描述 loginId 这么一个数据的。其实 meta 这个词,我们早就见过,在html
中,meta
标签就是用来描述这个 html 文档信息的
<!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></body>
</html>
还有著名的公司Facebook
,改了名字,叫Meta
,你就知道这个公司名真正的含义了
类装饰器
装饰器的本质
无论如何,在 JS 中,装饰器的本质是什么?虽然作用是提供元数据,但是并不是一个简单数据就能搞定的,因此装饰器本身就是就是一个函数,并且装饰器是属于 JS 的,并不是简简单单的 TS 的类型检查,是要参与运行的。
装饰器可以修饰:
- 类
- 成员(属性 + 方法)
- 参数
tsconfig 设置
由于现在装饰器没有正式形成规范,因此,在 TS 中使用装饰器,需要打开装饰器设置:
"experimentalDecorators": true
类装饰器
类装饰器本质是一个函数,该函数接收一个参数,表示类本身(构造函数本身)
使用装饰器 @
得到一个函数
在 TS 中,构造函数的表示
Function
new (...args:any[]) => any
// 定义为Function
function classDecoration(target: Function) {
console.log('classDecoration');
console.log(target);
}
@classDecoration
class A {}
// 定义为构造函数
function classDecoration(target: new (...args: any[]) => any) {
console.log('classDecoration');
console.log(target);
}
@classDecoration
class A {}
并且构造器是在定义这个类的时候,就会运行。而不是必须要等到你 new
对象的时候才会执行。
从执行之后的打印结果可以看出,target 就是这个类本身:
classDecoration
[class A]
上面的代码,我们编译之后,是下面的样子:
'use strict';
var __decorate =
(this && this.__decorate) ||
function (decorators, target, key, desc) {
var c = arguments.length,
r =
c < 3
? target
: desc === null
? (desc = Object.getOwnPropertyDescriptor(target, key))
: desc,
d;
if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function')
r = Reflect.decorate(decorators, target, key, desc);
else
for (var i = decorators.length - 1; i >= 0; i--)
if ((d = decorators[i]))
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
function classDecoration(target) {
console.log('classDecoration');
}
let A = class A {};
A = __decorate([classDecoration], A);
其实通过编译之后的代码,就可以看到,直接运行了__decorate
函数
泛型约束
我们之前讲过泛型构造函数,所以构造函数可以写成泛型的
// 泛型类型别名
type constructor<T = any> = new (...args: any[]) => T;
function classDecoration(target: constructor) {
console.log('classDecoration');
console.log(target);
}
@classDecoration
class A {}
这样,我们可以通过泛型约束,对要使用装饰器的类进行约束了
type constructor<T = any> = new (...args: any[]) => T;
type User = {
id: number;
name: string;
info(): void;
};
function classDecoration<T extends constructor<User>>(target: T) {
console.log('classDecoration');
console.log(target);
}
@classDecoration
class A {
constructor(public id: number, public name: string) {}
info() {}
}
我们说装饰器其实是个函数,我们也能通过像函数那样调用,甚至传参,但是现在第一个参数 target 给固定限制了,该怎么处理呢?
装饰器工厂模式
我们可以工厂模式就能轻松解决这个问题,普通函数,返回一个装饰器函数就行了
type constructor<T = any> = new (...args: any[]) => T;
function classDecorator<T extends constructor>(str: string) {
console.log('普通方法的参数:' + str);
return function (target: T) {
console.log('类装饰器' + str);
};
}
@classDecorator('hello')
class A {}
通过工厂模式既然能够返回一个函数,那么也能返回一个类,我们其实也能通过这种方式对原来的类进行修饰。
type constructor<T = any> = new (...args: any[]) => T;
function classDecorator<T extends constructor>(target: T) {
return class extends target {
public newProperty = 'new property';
public hello = 'override';
info() {
console.log('this is info');
}
};
}
@classDecorator
class A {
public hello = 'hello world';
}
const objA = new A();
console.log(objA.hello);
console.log((objA as any).newProperty); // 很明显,没有类型
(objA as any).info();
export {};
虽然可以这么做,但是很明显,返回的新的类,并不知道有新加的内容。
多装饰器
类装饰器不仅仅能写一个,还能写多个
type constructor<T = any> = new (...args: any[]) => T;
function classDecorator1<T extends constructor>(str: string) {
console.log('classDecorator1的参数:' + str);
return function (target: T) {
console.log('classDecorator1类装饰器' + str);
};
}
function classDecorator2<T extends constructor>(str: string) {
console.log('classDecorator2的参数:' + str);
return function (target: T) {
console.log('classDecorator2类装饰器' + str);
};
}
@classDecorator1('1')
@classDecorator2('2')
class A {}
不过,注意执行之后的打印顺序:
classDecorator1的参数:1
classDecorator2的参数:2
classDecorator2类装饰器2
classDecorator1类装饰器1
装饰器的执行很明显是从下到上的
属性装饰器
属性装饰器
属性装饰器也是一个函数,该函数至少需要两个参数
**参数一:**如果是静态属性,为类本身;如果是实例属性,为类的原型
**参数二:**字符串,表示属性名
function d(target: any, key: string) {
console.log(target, key);
console.log(target === A.prototype);
}
class A {
@d
prop1: string;
@d
prop2: string;
}
当然,属性装饰器也能写成工厂模式:
function d() {
return function d(target: any, key: string) {
console.log(target, key);
};
}
class A {
@d()
prop1: string;
@d()
prop2: string;
}
也可以传值进去
function d(value: string) {
return function d(target: any, key: string) {
target[key] = value;
};
}
class A {
@d('hello')
prop1: string;
@d('world')
prop2: string;
}
console.log(A.prototype);
注意,target 是类的原型,因此这里赋值其实是赋值在类原型上的,而不是实例上。
当属性为静态属性时,target 得到的结果是 A 的构造函数
function d() {
return function d(target: any, key: string) {
console.log(target, key);
};
}
class A {
@d()
prop1: string;
@d()
static prop2: string;
}
**补充:**当你尝试通过装饰器给属性赋值时,它实际上是在原型上设置了这些值,这意味着所有实例将共享这些属性值,而不是每个实例拥有自己的独立值。
如果你要解决这个问题,你需要确保装饰器在每个类实例创建时为实例属性赋值。这通常是通过在构造函数中设置这些属性来完成的,但是由于装饰器不能直接访问类的构造函数,我们可以使用一点策略来解决。
下面的做法需要设置:
"noImplicitAny": false,
function d(value: string) { return function (target: any, key: string) { if (!target.__initProperties) { target.__initProperties = function () { for (let prop in target.__props) { this[prop] = target.__props[prop]; } }; target.__props = {}; } target.__props[key] = value; }; } class A { @d('hello') prop1: string; @d('world') prop2: string; constructor() { if (typeof this['__initProperties'] === 'function') { this['__initProperties'](); } } } const a = new A(); console.log(a.prop1); // Output: "hello" console.log(a.prop2); // Output: "world"
方法装饰器
方法装饰器
方法装饰器也是一个函数,该函数至少需要三个参数
**参数一:如果是静态方法,为类本身(类构造函数类型);如果是实例方法,为类的原型(对象类型)
**参数二:**字符串,表示方法名
**参数三:**属性描述对象,其实就是 js 的 Object.defineProperty()中的属性描述对象{value:xxx,writable:xxx, enumerable:xxx, configurable:xxx}
上节课属性不是也讲过参数一也是这种情况吗?如果非要区分开静态方法和实例方法,其实分开设置也行:
function d0() {
return function d(target: Record<string, any>, key: string) {
console.log(target, key);
};
}
function d1() {
return function d(
target: Record<string, any>,
key: string,
descriptor: PropertyDescriptor
) {
console.log(target, key, descriptor);
};
}
function d2() {
return function d(
target: new (...args: any[]) => any,
key: string,
descriptor: PropertyDescriptor
) {
console.log(target, key, descriptor);
};
}
class A {
@d0()
prop1: string;
prop2: string;
@d1()
method1() {}
@d2()
static method2() {}
}
为了减少讲解的麻烦,这里还是直接用 any
function d() {
return function d(target: any, key: string, descriptor: PropertyDescriptor) {
console.log(target, key, descriptor);
};
}
class A {
prop1: string;
prop2: string;
@d()
method1() {}
}
const objA = new A();
for (let prop in objA) {
console.log(prop);
}
结果:
{} method1 {
value: [Function: method1],
writable: true,
enumerable: false,
configurable: true
}
prop1
prop2
通过结果可以看到,方法默认并没有遍历,因为enumerable: false
,那我们完全可以通过属性描述符进行修改
function enumerable() {
return function d(target: any, key: string, descriptor: PropertyDescriptor) {
console.log(target, key, descriptor);
descriptor.enumerable = true;
};
}
class A {
prop1: string;
prop2: string;
@enumerable()
method1() {}
}
const objA = new A();
for (let prop in objA) {
console.log(prop);
}
既然可以这么做,那么我们的操作性就大大增加了,比如我们完全可以修改属性描述符的 value 值,让其变为执行其他的内容
function enumerable() {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
console.log(target, key, descriptor);
descriptor.enumerable = true;
};
}
// 被废弃的方法
function noUse() {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.value = function () {
console.log('被废弃的方法');
};
};
}
class A {
prop1: string;
prop2: string;
@enumerable()
method1() {}
@enumerable()
@noUse()
method2() {
console.log('正常执行......');
}
}
const objA = new A();
for (let prop in objA) {
console.log(prop);
}
// 执行被废弃的方法
objA.method2();
甚至于,我们还能实现方法的拦截器
function enumerable() {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
console.log(target, key, descriptor);
descriptor.enumerable = true;
};
}
// 被废弃的方法
function noUse() {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.value = function () {
console.log('被废弃的方法');
};
};
}
function interceptor(str: string) {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
const temp = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log('前置拦截---' + str);
temp.call(this, args);
console.log('后置拦截---' + str);
};
};
}
class A {
prop1: string;
prop2: string;
@enumerable()
method1() {}
@enumerable()
@noUse()
method2() {
console.log('正常执行......');
}
@enumerable()
@interceptor('interceptor')
method3(str: string) {
console.log('正在执行 method3:' + str);
}
}
const objA = new A();
for (let prop in objA) {
console.log(prop);
}
// 执行被废弃的方法
objA.method2();
// 拦截
objA.method3('hello world');
访问器属性与参数装饰器
访问器属性装饰器
**参数一:**类的原型(对象类型)
**参数二:**字符串,表示方法名
参数三: 属性描述对象,其实就是 js 的 Object.defineProperty()中的属性描述对象
function d(str: string) {
return function d<T>(
target: any,
key: string,
descriptor: TypedPropertyDescriptor<T>
) {
console.log(target, key);
const temp = descriptor.set!;
descriptor.set = function (value: T) {
console.log('前置', str);
temp.call(this, value);
console.log('后置', str);
};
};
}
class User {
public id: number;
public name: string;
private _age: number;
@d('hello')
set age(v: number) {
console.log('set', v);
this._age = v;
}
}
const u = new User();
u.age = 10;
方法参数装饰器
方法参数几乎和属性装饰器一致,只是多了一个属性
**参数一:**如果是静态属性,为类本身;如果是实例属性,为类的原型
**参数二:**字符串,表示方法名
**参数三:**表示参数顺序
function paramDecorator(target: any, key: string, index: number) {
console.log(target, key, index);
}
class A {
method1(@paramDecorator id: number, @paramDecorator name: string) {
console.log('---', id, name);
}
}
const objA = new A();
objA.method1(1, 'hello');
当然,也能写成工厂模式
function paramDecorator() {
return function (target: any, key: string, index: number) {
console.log(target, key, index);
};
}
class A {
method1(@paramDecorator() id: number, @paramDecorator() name: string) {
console.log('---', id, name);
}
}
const objA = new A();
objA.method1(1, 'hello');
我们稍微处理一下,在原型上加上属性看看效果:
function paramDecorator(paramName: string) {
return function (target: any, key: string, index: number) {
!target.__params && (target.__params = {});
target.__params[index] = paramName;
};
}
class A {
method1(
@paramDecorator('id') id: number,
@paramDecorator('name') name: string
) {
console.log('---', id, name);
}
}
const objA = new A();
console.log(A.prototype); // { __params: { '0': 'id', '1': 'name' } }
reflect-metadata
reflect-metadata
reflect-metadata
是一个 JavaScript 库,用于在运行时访问和操作装饰器的元数据。它提供了一组 API,可以读取和写入装饰器相关的元数据信息。
我上面通过自己封装函数来处理类和类成员相关的元数据,但是相关能力比较薄弱,借助 Reflect-metadata 来解决提供元数据的处理能力。
安装
npm install reflect-metadata
tsconfig.json
设置
"experimentalDecorators": true,
"emitDecoratorMetadata": true
引入
import 'reflect-metadata';
基本语法
定义元数据
声明性定义:
@Reflect.metadata(metadataKey, metadataValue)
@Reflect.metadata('classType', 'A类-1')
class A {
prop1: string;
method() {}
}
命令式定义:
Reflect.defineMetadata(metadataKey, metadataValue, 定义元数据的对象, propertyKey?);
class A {
prop1: string;
method() {}
}
Reflect.defineMetadata('classType', 'A类-2', A);
获取元数据
Reflect.getMetadata(metadataKey, 定义元数据类):返回metadataValue
console.log(Reflect.getMetadata('classType', A));
工厂模式
也可以将上面的处理封装为工厂模式,使用起来更加方便
方式 1:
const ClassTypeMetaKey = Symbol('classType');
function ClassType(type: string) {
return Reflect.metadata(ClassTypeMetaKey, type);
}
@ClassType('A类-1')
class A {
prop1: string;
method() {}
}
console.log(Reflect.getMetadata(ClassTypeMetaKey, A));
方式 2:
type constructor<T = any> = new (...args: any[]) => T;
const ClassTypeMetaKey = Symbol('classType');
function ClassType(type: string) {
return <T extends constructor>(target: T) => {
Reflect.defineMetadata(ClassTypeMetaKey, type, target);
};
}
@ClassType('A类-2')
class A {
prop1: string;
method() {}
}
console.log(Reflect.getMetadata(ClassTypeMetaKey, A));
成员属性和方法的处理
基本语法 API 都基本差不多,不过属性和方法是有两种状态的,实例的和静态的,对应的对象分别是对象原型和类本身
class A {
// @Reflect.metadata("propType1", "prop1-value")
prop1: string;
// @Reflect.metadata("propType2", "prop2-value")
static prop2: string;
@Reflect.metadata('methodType1', 'method1-value')
method1() {}
@Reflect.metadata('methodType2', 'method2-value')
static method2() {}
}
Reflect.defineMetadata('propType1', 'prop1-value', A.prototype, 'prop1');
Reflect.defineMetadata('propType2', 'prop2-value', A, 'prop2');
console.log(Reflect.getMetadata('propType1', A.prototype, 'prop1'));
console.log(Reflect.getMetadata('propType2', A, 'prop2'));
console.log(Reflect.getMetadata('methodType1', A.prototype, 'method1'));
console.log(Reflect.getMetadata('methodType2', A, 'method2'));
我们可以稍微封装一下,简单的得到一些我们想要的效果:
const formatMetadataKey = Symbol('format');
function format(formatString: string) {
return Reflect.metadata(formatMetadataKey, formatString);
}
function getFormat(target: any, propertyKey: string) {
return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}
class Greeter {
@format('Hello, %s')
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
let formatString = getFormat(this, 'greeting');
return formatString.replace('%s', this.greeting);
}
}
const objG = new Greeter('world');
// console.log(objG.greet()); // "Hello, world"
const objG = new Greeter('world');
// console.log(objG.greet());
// greet封装在外面也是一样的道理
function greet(obj: any, key: string) {
let formatString = getFormat(obj, key);
return formatString.replace('%s', obj[key]);
}
const g = greet(objG, 'greeting');
console.log(g);
元数据相关功能库
class-transformer
给大家介绍两个基于reflect-metadata元数据实现的比较有用的功能库
class-transformer可以很方便的将普通对象转换为类的某些实例,这个功能在某一些时候非常好用。
比如我们在很多时候,从后端获取的数据,都是一些简单的 json 格式的数据,有些数据可能需要经过前端的再处理,如下:
{
"id": 1,
"firstName": "Nancy",
"lastName": "Lopez",
"age": 35
}
为了简单方便,可以使用远程的 mock 模拟数据,比如easy mock,直接简单登录之后即可使用,使用过程就两步:
1、创建项目
2、创建接口
再复杂一点的时候可以自己去阅读网站的文档
我们可以创建如下的简单数据
{
"code": 200,
"data|10": [
{
"id|+1": 1,
"firstName": "@first",
"lastName": "@last",
"age|9-45": 1
}
],
"msg": "成功"
}
从后端获取的是上面的数据,可能前端还需要一些功能,比如获取全名,比如判断是否成年,我们可以创建一个类进行封装处理
class User {
id: number;
firstName: string;
lastName: string;
age: number;
getFullName() {
return this.firstName + ' ' + this.lastName;
}
isAdult() {
return this.age > 18 ? '成年人' : '未成年人';
}
}
// 模拟数据返回格式
interface Result<T> {
code: number;
data: T;
msg: string;
}
这在我们获取数据的时候,如果直接获取的就是简单 json 数据,倒是没什么影响,但是不能访问自己封装的函数
fetch('https://mock.mengxuegu.com/mock/65b1f3d1c4cd67421b34cd0c/mock_ts/list')
.then((res) => res.json())
.then((res: Result<User[]>) => {
console.log(res.code);
console.log(res.msg);
const users = res.data;
for (const u of users) {
console.log(u.id + ' ' + u.firstName);
// console.log(u.id + " " + u.getFullName() + " " + u.isAdult()); //error
}
});
这里,就可以使用class-transformer
它可以自动的将数据和我们封装的类进行映射,使用也非常的简单
import 'reflect-metadata';
import { plainToInstance } from 'class-transformer';
fetch('https://mock.mengxuegu.com/mock/65b1f3d1c4cd67421b34cd0c/mock_ts/list')
.then((res) => res.json())
.then((res: Result<User[]>) => {
console.log(res.code);
console.log(res.msg);
const users = res.data;
const us = plainToInstance(User, users);
for (const u of us) {
// console.log(u.id + " " + u.firstName);
console.log(u.id + ' ' + u.getFullName() + ' ' + u.isAdult());
}
});
这样就正常的获取了 User 类所修饰的内容
class-validator
这个库同样是基于reflect-metadata元数据实现的比较有用的功能库,通过名字大家就知道,这个库可以用来对类进行验证
这个库使用也非常的简单,基本也就知晓两步就 ok
1、相关装饰器的绑定
2、验证方法的调用
import 'reflect-metadata';
import {
validate,
IsNotEmpty,
Length,
Min,
Max,
IsPhoneNumber,
} from 'class-validator';
class User {
@IsNotEmpty({ message: '账号不能为空' })
@Length(3, 5, { message: '账号必须是3-5个字符' })
loginId: string;
@Min(9)
@Max(45)
age: number;
@IsPhoneNumber('CN')
tel: string;
}
const u = new User();
validate(u).then((errors) => {
console.log(errors);
});