课程名称:web前端架构师
课程章节:第16周 第八章 编写作品相关代码以及学习装饰器的使用
主讲老师:张轩
课程内容:使用装饰器完成用户输入验证
装饰器的使用
TypeScript 中的装饰器使用 @expression 这种形式,expression 求值后为一个函数,它在运行时被调用,被装饰的声明信息会被做为参数传入。
Javascript规范里的装饰器目前处在 建议征集的第二阶段,https://github.com/tc39/proposal-decorators 也就意味着不能在原生代码中直接使用,浏览器暂不支持。
TypeScript 工具在编译阶段,把装饰器语法转换成浏览器可执行的代码。
// tsconfig
{
"compilerOptions": {
"experimentalDecorators": true
}
}
装饰器的基本使用
这里我们主要使用的是方法装饰器, 其他的可以参考。https://es6.ruanyifeng.com/#docs/decorator
装饰器的本意是要“装饰”类的实例,但是这个时候实例还没生成,所以只能去装饰原型(这不同于类的装饰,那种情况时target参数指的是类本身);第二个参数是所要装饰的属性名,第三个参数是该属性的描述对象。
function fn1(prototype: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('fn', prototype, propertyKey, descriptor);
}
function fn2(prototype: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('fn2', prototype, propertyKey, descriptor);
}
class Car {
constructor() {
console.log('car');
}
@fn1
@fn2
run() {
console.log('run');
}
}
const car = new Car();
car.run();
运行后, 发现 fn2 装饰器先运行
可以通过修改 descriptor 的值来修改装饰的方法
function fn1(prototype: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('fn', prototype, propertyKey, descriptor);
const originMethod = descriptor.value;
descriptor.value = function() {
console.log('fn start');
originMethod.call(this);
console.log('fn end');
};
}
function fn2(prototype: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('fn2', prototype, propertyKey, descriptor);
// 获取装饰方法的本身
const originMethod = descriptor.value;
descriptor.value = function() {
console.log('fn2 start');
originMethod.call(this);
console.log('fn2 end');
};
}
class Car {
constructor() {
console.log('car');
}
@fn1
@fn2
run() {
console.log('run');
}
}
const car = new Car();
car.run();
通过工厂函数可以给装饰器传递参数
function fn1(num: number) {
return (prototype: any, propertyKey: string, descriptor: PropertyDescriptor) => {
console.log('fn', prototype, propertyKey, descriptor);
const originMethod = descriptor.value;
descriptor.value = function() {
originMethod.call(this);
};
};
}
function fn2() {
return (prototype: any, propertyKey: string, descriptor: PropertyDescriptor) => {
console.log('fn2', prototype, propertyKey, descriptor);
const originMethod = descriptor.value;
descriptor.value = function() {
originMethod.call(this);
};
};
}
class Car {
constructor() {
console.log('car');
}
@fn1(123)
@fn2(222)
run() {
console.log('run');
}
}
const car = new Car();
car.run();
使用装饰器完成用户输入验证
首先将错误信息提取到一个目录下,新建一个 error 目录
// app/error/index.ts
import userErrMsg from './user';
import workErrMsg from './work';
const errMsg = {
...userErrMsg,
...workErrMsg,
};
export default errMsg;
// app/error/user.ts
export default {
userValidateFail: {
errno: 101001,
msg: '输入信息验证失败',
},
createUserExists: {
errno: 101002,
msg: '该邮箱已经注册过,请直接登录',
},
signinValidateFail: {
errno: 101003,
msg: '用户名或密码错误',
},
signValidateFail: {
errno: 101004,
msg: '用户验证失败',
},
phoneValidateFail: {
errno: 101005,
msg: '手机号格式错误',
},
sendCodeFrequentlyFail: {
errno: 101006,
msg: '请勿频繁获取短信验证码',
},
signinCorrectCodeFail: {
errno: 101007,
msg: '验证码输入错误',
},
};
// app/error/work.ts
export default {
workValidateFail: {
errno: 102001,
msg: '输入信息验证失败',
},
};
用户输入验证装饰器
首先我们期望我们使用时下面这种方式使用的,只需要传入 rules 和 errMsg ,就可以通过装饰器进行校验
import { Controller } from 'egg';
const rules = {
title: {
requireds: true,
type: 'string',
},
};
export default class Work extends Controller {
@inputValidate(rules, 'workValidateFail')
async createWork() {
const { ctx, service } = this;
const res = await service.work.createWork();
ctx.helper.success({ ctx, res });
}
}
编写装饰器代码
// app/decorator/inputValidate.ts
import { Controller } from 'egg';
import { errMsgType } from '../error';
const inputValidate = (rules: any, errType: errMsgType) => {
return (_target, _propertyKey, descriptor: PropertyDescriptor) => {
const originMethod = descriptor.value;
descriptor.value = function(...args:any[]) {
const that = this as Controller;
// 这里 ts 会报错,但是我们知道我们是在 Controller 类或子类的方法中运行的,所以把它给忽略掉
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const { ctx, app } = that;
const errors = app.validator.validate(rules, ctx.request.body);
if (errors) {
// 出现错误,直接返回错误信息
return ctx.helper.error({ ctx, errType, err: errors });
}
// 调用属性本身的方法
return originMethod.apply(this, args);
};
};
};
export default inputValidate;
然后运行代码,就可以通过装饰器的形式 @inputValidate(rules, 'workValidateFail')
来进行参数校验,所有的参数校验也都可以这样使用,大大提高了代码的复用。
共同学习,写下你的评论
评论加载中...
作者其他优质文章