前言

关键字
class-validator、class-transformer、plain-object(平面对象)、myjson

参考资料

notes

本节课要介绍的两个库 class-validator 和 class-transformer,它们是基于上一节介绍的 reflect-metadata 实现的。
它们的具体用法,直接查阅文档即可,下面简单认识一下它们有啥用。

class-validator
这个库是用来做验证的,使用这个库提供的装饰器工厂,会成员进行校验。
常见的校验规则,这个库中都有提供,比如:

  • 该属性是否必填
  • 该属性的最大值
  • 该属性的最小值
  • 该属性的长度
  • 。。。

装饰器工厂:返回一个装饰器。

class-transforme
这个库的作用:将一个平面对象转换为类。

说起来不容易理解,可以参考:链接

codes

class-validator

  1. import { IsNotEmpty, validate } from "class-validator";
  2. class User {
  3. @IsNotEmpty()
  4. loginId: string
  5. loginPwd: string
  6. age: number
  7. }
  8. const u = new User();
  9. validate(u).then(errors => {
  10. console.log(errors);
  11. });

@IsNotEmpty() 该装饰器的作用:被装饰的属性必填。

image.png

  1. {
  2. target: Object; // Object that was validated.
  3. property: string; // Object's property that haven't pass validation.
  4. value: any; // Value that haven't pass a validation.
  5. constraints?: { // Constraints that failed validation with error messages.
  6. [type: string]: string;
  7. };
  8. children?: ValidationError[]; // Contains all nested validation errors of the property
  9. }
  • target当前验证的目标对象
  • property当前验证的是哪个属性
  • value属性的值
  • constraints验证失败时展示的错误信息
  • children包含属性的所有嵌套验证错误

这个库里边有很多东西都是可以配置的,比如错误信息。在没有指定错误信息的情况下,打印的默认提示信息是 loginId should not be empty

  1. import { IsNotEmpty, validate } from "class-validator";
  2. class User {
  3. @IsNotEmpty({ message: "账号不可以为空" })
  4. loginId: string;
  5. loginPwd: string;
  6. age: number;
  7. }
  8. const u = new User();
  9. validate(u).then((errors) => {
  10. console.log(errors);
  11. });

image.png

ValidationError 只有在验证无法通过的情况下,才会打印,否则 errors 为一个空数组。

  1. import { IsNotEmpty, validate } from "class-validator";
  2. class User {
  3. @IsNotEmpty({ message: "账号不可以为空" })
  4. loginId: string;
  5. loginPwd: string;
  6. age: number;
  7. }
  8. const u = new User();
  9. u.loginId = "admin";
  10. validate(u).then((errors) => {
  11. console.log(errors); // => []
  12. });
  1. import { IsNotEmpty, MaxLength, MinLength, validate } from "class-validator";
  2. class User {
  3. @IsNotEmpty({ message: "账号不可以为空" })
  4. @MinLength(5, { message: "账号长度必须大于 5 个字符" })
  5. @MaxLength(12, { message: "账号长度必须小于 12 个字符" })
  6. loginId: string;
  7. loginPwd: string;
  8. age: number;
  9. }
  10. const u = new User();
  11. validate(u).then((errors) => {
  12. console.log(errors);
  13. });

image.png

constraints中包含所有导致验证未通过的原因。

仔细观察会发现返回的 errors 是一个数组,数组中成员的数量取决于当前验证未通过的属性的数量。如果 x 个属性验证未通过,那么数组长度就是 x

class-transformer

初始化 json 数据接口,json 数据就先用官方(链接)里边的 user.json。为了更好地模拟网络请求,可以在 myjson 上提前初始化一个接口,以便后续调用。

image.png

类似 myjson 的在线服务还有很多,比如 jsonserve 也可以帮我们模拟在线的 json 数据。 具体操作上都是一致的。

image.png

⚠️ url 中的 http 改为 https,如果使用 http 的话,fetch 会报错。

当然,也可以不使用 myjson 来模拟接口,可以直接创建一个 user.json 文件,然后将相关数据丢里边。直接通过相对路径来请求这里边的内容即可。

  1. import axios from "axios";
  2. axios
  3. .get("https://myjson.dit.upm.es/api/bins/9r7d")
  4. .then((resp) => resp.data)
  5. .then((data) => {
  6. console.log(data);
  7. });

image.png

🤔 为啥不直接使用 fetch 这个 api 来测试,而是通过 axios 这个第三方库?
⚠️ fetch 是 H5 的一个 api,在 node 环境下没法使用。

❌ 访问接口是报错,写法和老师是一致的,报错可能是由 myjson 提供的接口的自身问题所导致的。改用 jsonserve 来模拟 json 数据,就不会报错。

image.png

image.png

plain-object
{ id: 1, firstName: 'Johny', lastName: 'Cage', age: 27 } 这就是一个平面对象(plain-object)

  1. export class User {
  2. id: number;
  3. firstName: string;
  4. lastName: string;
  5. age: number;
  6. getName() {
  7. return this.firstName + " " + this.lastName;
  8. }
  9. isAdult() {
  10. return this.age > 36 && this.age < 60;
  11. }
  12. }
  13. import axios from "axios";
  14. axios
  15. .get("https://api.jsonserve.com/_DhTgU")
  16. .then((resp) => resp.data)
  17. .then((users) => {
  18. for (const u of users) {
  19. u.getName(); // => TypeError: u.getName is not a function
  20. }
  21. });

**u.getName()**
这么做,显然是会报错的,因为响应结果中压根就不存在 getName 方法。
此时就需要用到 class-transformer 这个库了,它可以将 plain-object 转为 class-object。

  1. import { plainToClass } from "class-transformer";
  2. export class User {
  3. id: number;
  4. firstName: string;
  5. lastName: string;
  6. age: number;
  7. getName() {
  8. return this.firstName + " " + this.lastName;
  9. }
  10. isAdult() {
  11. return this.age > 36 && this.age < 60;
  12. }
  13. }
  14. import axios from "axios";
  15. axios
  16. .get("https://api.jsonserve.com/_DhTgU")
  17. .then((resp) => resp.data)
  18. .then((users: User[]) => {
  19. for (const u of users) {
  20. const user = plainToClass(User, u);
  21. console.log(user.getName(), user.isAdult());
  22. }
  23. });

user 类型推断结果和 u 一致,都是 User

image.png

**const user = plainToClass(User, u)**
将 plain-object u 转为 class-object User 实例

  1. import { plainToClass } from "class-transformer";
  2. export class User {
  3. id: number;
  4. firstName: string;
  5. lastName: string;
  6. age: number;
  7. getName() {
  8. return this.firstName + " " + this.lastName;
  9. }
  10. isAdult() {
  11. return this.age > 36 && this.age < 60;
  12. }
  13. }
  14. import axios from "axios";
  15. axios
  16. .get("https://api.jsonserve.com/_DhTgU")
  17. .then((resp) => resp.data)
  18. .then((users: User[]) => {
  19. const us = plainToClass(User, users);
  20. for (const u of us) {
  21. console.log(u.getName(), u.isAdult());
  22. }
  23. });

us 类型推断结果和 users 一致,都是 User[]

  1. import { plainToClass } from "class-transformer";
  2. export class User {
  3. id: number;
  4. firstName: string;
  5. lastName: string;
  6. age: number;
  7. getName() {
  8. return this.firstName + " " + this.lastName;
  9. }
  10. isAdult() {
  11. return this.age > 36 && this.age < 60;
  12. }
  13. }
  14. import axios from "axios";
  15. axios
  16. .get("https://api.jsonserve.com/_DhTgU")
  17. .then((resp) => resp.data)
  18. .then((users: User[]) => {
  19. const us = plainToClass(User, users);
  20. for (const u of us) {
  21. console.log(typeof u.age, u.age);
  22. }
  23. });

此时接口返回的数据是满足条件的
image.png

🤔 如果我们将返回的 age 类型改为字符串,那么 .ts 文件是否会报错?
答:不会。
因为 ts 并不会在运行时提供类型检查,运行时跑的是 js 代码。而我们写的 users: User[]const us = plainToClass(User, users)其实就已经告诉了 ts:us 的类型是 User[],也就是说,类型检查是可以通过的。

  1. [
  2. {
  3. "id": 1,
  4. "firstName": "Johny",
  5. "lastName": "Cage",
  6. "age": "27"
  7. },
  8. {
  9. "id": 2,
  10. "firstName": "Ismoil",
  11. "lastName": "Somoni",
  12. "age": "50"
  13. },
  14. {
  15. "id": 3,
  16. "firstName": "Luke",
  17. "lastName": "Dacascos",
  18. "age": "12"
  19. }
  20. ]
  1. import { plainToClass } from "class-transformer";
  2. import axios from "axios";
  3. export class User {
  4. id: number;
  5. firstName: string;
  6. lastName: string;
  7. age: number;
  8. getName() {
  9. return this.firstName + " " + this.lastName;
  10. }
  11. isAdult() {
  12. return this.age > 36 && this.age < 60;
  13. }
  14. }
  15. axios
  16. .get("https://api.jsonserve.com/uL2oka")
  17. .then((resp) => resp.data)
  18. .then((users: User[]) => {
  19. const us = plainToClass(User, users);
  20. for (const u of us) {
  21. console.log(typeof u.age, u.age);
  22. }
  23. });

按照要求修改了 json 数据,更新了 url。

image.png

🤔 如何确保 plain-object 转为 class-object 后,age 是 number 类型?
使用 @Type 来实现,参考:链接

  1. import "reflect-metadata";
  2. import { plainToClass, Type } from "class-transformer";
  3. import axios from "axios";
  4. export class User {
  5. id: number;
  6. firstName: string;
  7. lastName: string;
  8. @Type(() => Number)
  9. age: number;
  10. getName() {
  11. return this.firstName + " " + this.lastName;
  12. }
  13. isAdult() {
  14. return this.age > 36 && this.age < 60;
  15. }
  16. }
  17. axios
  18. .get("https://api.jsonserve.com/uL2oka")
  19. .then((resp) => resp.data)
  20. .then((users: User[]) => {
  21. const us = plainToClass(User, users);
  22. for (const u of us) {
  23. console.log(typeof u.age, u.age);
  24. }
  25. });

image.png

@Type(() => Number)
⚠️ 不要将 Number 写为 number
可以这么理解:我们写的装饰器,会转为 js 代码,是要参与最终运行的。而 js 中并没有 number,number、string、boolean 。。。 这些东西是 ts 中给我们提供的,不要和 js 混淆。

⚠️ Type 这个 api 需要依赖 reflect-metadata 库