前言
关键字
class-validator、class-transformer、plain-object(平面对象)、myjson
参考资料
notes
本节课要介绍的两个库 class-validator 和 class-transformer,它们是基于上一节介绍的 reflect-metadata 实现的。
它们的具体用法,直接查阅文档即可,下面简单认识一下它们有啥用。
class-validator
这个库是用来做验证的,使用这个库提供的装饰器工厂,会成员进行校验。
常见的校验规则,这个库中都有提供,比如:
- 该属性是否必填
- 该属性的最大值
- 该属性的最小值
- 该属性的长度
- 。。。
装饰器工厂:返回一个装饰器。
class-transforme
这个库的作用:将一个平面对象转换为类。
说起来不容易理解,可以参考:链接
codes
class-validator
import { IsNotEmpty, validate } from "class-validator";
class User {
@IsNotEmpty()
loginId: string
loginPwd: string
age: number
}
const u = new User();
validate(u).then(errors => {
console.log(errors);
});
@IsNotEmpty()
该装饰器的作用:被装饰的属性必填。
{
target: Object; // Object that was validated.
property: string; // Object's property that haven't pass validation.
value: any; // Value that haven't pass a validation.
constraints?: { // Constraints that failed validation with error messages.
[type: string]: string;
};
children?: ValidationError[]; // Contains all nested validation errors of the property
}
target
当前验证的目标对象property
当前验证的是哪个属性value
属性的值constraints
验证失败时展示的错误信息children
包含属性的所有嵌套验证错误
这个库里边有很多东西都是可以配置的,比如错误信息。在没有指定错误信息的情况下,打印的默认提示信息是 loginId should not be empty
import { IsNotEmpty, validate } from "class-validator";
class User {
@IsNotEmpty({ message: "账号不可以为空" })
loginId: string;
loginPwd: string;
age: number;
}
const u = new User();
validate(u).then((errors) => {
console.log(errors);
});
ValidationError 只有在验证无法通过的情况下,才会打印,否则 errors
为一个空数组。
import { IsNotEmpty, validate } from "class-validator";
class User {
@IsNotEmpty({ message: "账号不可以为空" })
loginId: string;
loginPwd: string;
age: number;
}
const u = new User();
u.loginId = "admin";
validate(u).then((errors) => {
console.log(errors); // => []
});
import { IsNotEmpty, MaxLength, MinLength, validate } from "class-validator";
class User {
@IsNotEmpty({ message: "账号不可以为空" })
@MinLength(5, { message: "账号长度必须大于 5 个字符" })
@MaxLength(12, { message: "账号长度必须小于 12 个字符" })
loginId: string;
loginPwd: string;
age: number;
}
const u = new User();
validate(u).then((errors) => {
console.log(errors);
});
constraints
中包含所有导致验证未通过的原因。
仔细观察会发现返回的 errors 是一个数组,数组中成员的数量取决于当前验证未通过的属性的数量。如果 x 个属性验证未通过,那么数组长度就是 x
class-transformer
初始化 json 数据接口,json 数据就先用官方(链接)里边的 user.json。为了更好地模拟网络请求,可以在 myjson 上提前初始化一个接口,以便后续调用。
类似 myjson 的在线服务还有很多,比如 jsonserve 也可以帮我们模拟在线的 json 数据。 具体操作上都是一致的。
⚠️ url 中的 http 改为 https,如果使用 http 的话,fetch 会报错。
当然,也可以不使用 myjson 来模拟接口,可以直接创建一个 user.json 文件,然后将相关数据丢里边。直接通过相对路径来请求这里边的内容即可。
import axios from "axios";
axios
.get("https://myjson.dit.upm.es/api/bins/9r7d")
.then((resp) => resp.data)
.then((data) => {
console.log(data);
});
🤔 为啥不直接使用 fetch 这个 api 来测试,而是通过 axios 这个第三方库?
⚠️ fetch 是 H5 的一个 api,在 node 环境下没法使用。
❌ 访问接口是报错,写法和老师是一致的,报错可能是由 myjson 提供的接口的自身问题所导致的。改用 jsonserve 来模拟 json 数据,就不会报错。
plain-object{ id: 1, firstName: 'Johny', lastName: 'Cage', age: 27 }
这就是一个平面对象(plain-object)
export class User {
id: number;
firstName: string;
lastName: string;
age: number;
getName() {
return this.firstName + " " + this.lastName;
}
isAdult() {
return this.age > 36 && this.age < 60;
}
}
import axios from "axios";
axios
.get("https://api.jsonserve.com/_DhTgU")
.then((resp) => resp.data)
.then((users) => {
for (const u of users) {
u.getName(); // => TypeError: u.getName is not a function
}
});
**u.getName()**
这么做,显然是会报错的,因为响应结果中压根就不存在 getName 方法。
此时就需要用到 class-transformer 这个库了,它可以将 plain-object 转为 class-object。
import { plainToClass } from "class-transformer";
export class User {
id: number;
firstName: string;
lastName: string;
age: number;
getName() {
return this.firstName + " " + this.lastName;
}
isAdult() {
return this.age > 36 && this.age < 60;
}
}
import axios from "axios";
axios
.get("https://api.jsonserve.com/_DhTgU")
.then((resp) => resp.data)
.then((users: User[]) => {
for (const u of users) {
const user = plainToClass(User, u);
console.log(user.getName(), user.isAdult());
}
});
user 类型推断结果和 u 一致,都是 User
**const user = plainToClass(User, u)**
将 plain-object u
转为 class-object User 实例
import { plainToClass } from "class-transformer";
export class User {
id: number;
firstName: string;
lastName: string;
age: number;
getName() {
return this.firstName + " " + this.lastName;
}
isAdult() {
return this.age > 36 && this.age < 60;
}
}
import axios from "axios";
axios
.get("https://api.jsonserve.com/_DhTgU")
.then((resp) => resp.data)
.then((users: User[]) => {
const us = plainToClass(User, users);
for (const u of us) {
console.log(u.getName(), u.isAdult());
}
});
us 类型推断结果和 users 一致,都是 User[]
import { plainToClass } from "class-transformer";
export class User {
id: number;
firstName: string;
lastName: string;
age: number;
getName() {
return this.firstName + " " + this.lastName;
}
isAdult() {
return this.age > 36 && this.age < 60;
}
}
import axios from "axios";
axios
.get("https://api.jsonserve.com/_DhTgU")
.then((resp) => resp.data)
.then((users: User[]) => {
const us = plainToClass(User, users);
for (const u of us) {
console.log(typeof u.age, u.age);
}
});
此时接口返回的数据是满足条件的
🤔 如果我们将返回的 age 类型改为字符串,那么 .ts 文件是否会报错?
答:不会。
因为 ts 并不会在运行时提供类型检查,运行时跑的是 js 代码。而我们写的 users: User[]
、const us = plainToClass(User, users)
其实就已经告诉了 ts:us
的类型是 User[]
,也就是说,类型检查是可以通过的。
[
{
"id": 1,
"firstName": "Johny",
"lastName": "Cage",
"age": "27"
},
{
"id": 2,
"firstName": "Ismoil",
"lastName": "Somoni",
"age": "50"
},
{
"id": 3,
"firstName": "Luke",
"lastName": "Dacascos",
"age": "12"
}
]
import { plainToClass } from "class-transformer";
import axios from "axios";
export class User {
id: number;
firstName: string;
lastName: string;
age: number;
getName() {
return this.firstName + " " + this.lastName;
}
isAdult() {
return this.age > 36 && this.age < 60;
}
}
axios
.get("https://api.jsonserve.com/uL2oka")
.then((resp) => resp.data)
.then((users: User[]) => {
const us = plainToClass(User, users);
for (const u of us) {
console.log(typeof u.age, u.age);
}
});
按照要求修改了 json 数据,更新了 url。
🤔 如何确保 plain-object 转为 class-object 后,age 是 number 类型?
使用 @Type
来实现,参考:链接
import "reflect-metadata";
import { plainToClass, Type } from "class-transformer";
import axios from "axios";
export class User {
id: number;
firstName: string;
lastName: string;
@Type(() => Number)
age: number;
getName() {
return this.firstName + " " + this.lastName;
}
isAdult() {
return this.age > 36 && this.age < 60;
}
}
axios
.get("https://api.jsonserve.com/uL2oka")
.then((resp) => resp.data)
.then((users: User[]) => {
const us = plainToClass(User, users);
for (const u of us) {
console.log(typeof u.age, u.age);
}
});
@Type(() => Number)
⚠️ 不要将 Number 写为 number
可以这么理解:我们写的装饰器,会转为 js 代码,是要参与最终运行的。而 js 中并没有 number,number、string、boolean 。。。 这些东西是 ts 中给我们提供的,不要和 js 混淆。
⚠️ Type 这个 api 需要依赖 reflect-metadata 库