什么是Decoretor
- Decoretor是在
声明阶段
实现类与类的成员注解的一种语法, 其本质就是一种特殊的函数 - Decoretor有两种使用方法, 一种是修饰类, 另一种是修饰类的成员, 其中, 类成员, 包括类属性成员和类方法成员
- 如果一个类和类的方法都是用了Decorator,类方法的Decorator优先于类的Decorator执行
@Controller
class Hello{
}
// 等同于
Controller(Hello)
@log
class Numberic {
@readoly PI = Math.PI;
@validate
add(...params) {
return nums.reduce((P, n) => P + n, 0)
}
}
在js中, decoretor大致长这样, @log
是修饰类的装饰器, @readonly
是修饰类的属性的装饰器, @validate
是修饰类的方法的装饰器,
修饰类的装饰器
修饰类的装饰器, 其参数就是被修饰的类, 在上面例子中, @log
的方法接受的参数即Numberic
function log() {
const desc = Object.getOwnPropertyDescriptors(target.prototype);
for (const key of Object.keys(desc)) {
if(key === 'constructor'){
continue;
}
// 获取属性,
//这里需要注意, 在es6中, 类的属性不一定是一个字符串常量,
const func = desc[key].value;
// 只关心类的方法
if('function' === typeof func){
// 重新定义类的原方法,
Object.defineProperty(target.prototype, key, {
value(...args) {
console.log('before: ' + key);
const ret = func.apply(this.args);
console.log('after: ' + key);
return ret;
}
})
}
}
}
修饰类成员的Decoretor
修饰类成员的Decoretor
参数有三个, 分别是类的实例对象, 属性名称, 以及该类成员的描述符
function readonly(target, key, desc){
// 我们只是希望该属性不可修改, 因此只需要将描述符的writable属性设为false
desc.writable = false;
}
修饰类方法的Decortor
类方法成员的装饰器参数同类属性的装饰器一样, 接受类的实例对象, 属性名称, 以及该类成员的描述符三个参数
function validate(target, key, desc){
const func = desc.value;
desc.value = function(...args){
// 遍历入参
for(let num of args){
if('number' !== typeof num){
throw new Error(`"${num}" is not a number`);
}
}
return func.apply(this, args);
}
}
带参数的装饰器
带参数的装饰器需在外层包裹一层函数接受参数
// Decorator不传参
function Controller(target) {
}
// Decorator传参
function Controller(params) {
return function (target) {
}
}
如果Decorator是传参的,即使params有默认值,在调用时必须带上括号,即:
@Controller()
class Hello{
}
koa中用装饰器描述应用路由
先看看传统的koa写法(不带装饰器)
var Koa = require('koa');
var Router = require('koa-router');
var app = new Koa();
var router = new Router();
router.get('/', async (ctx, next) => {
// ctx.router available
});
app
.use(router.routes())
.use(router.allowedMethods());
使用装饰器之后的代码:
const router = require('../router');
@router()
class Home {
@router.get('/')
async home(ctx) {
await ctx.render('index');
}
}
module.exports = Home;
可以看出, 使用装饰器之后的应用路由更简洁,
- 首先我们先实现修饰controller类的装饰器, 该装饰器接受一个config, 作用同
koa-router
的config
const KoaRouter = require("koa-router");
function router(config = {}) {
const router = new KoaRouter(config)
return function (target) {
const functions = Object.getOwnPropertyDescriptors(target.prototype)
for (let v in functions) {
// 排除类的构造方法
if (v !== 'constructor') {
let fn = functions[v].value
fn(router)
}
}
// 返回router实例, 供类成员的装饰器使用
return router
}
}
接着, 我们再实现类的成员的装饰器
const methods = ['get', 'post', 'put', 'delete', 'options', 'head', 'patch'];
methods.forEach(method => {
router[method] = url => {
return function (target, name, descriptor) {
let fn = descriptor.value
descriptor.value = (router) => {
router[method](url, async(ctx, next) => {
await fn(ctx, next)
})
}
}
}
})
说句题外话: 在koa
中, 我们通过ctx.body = xxx
来实现接口返回数据.
有了上面装饰器, 我们可以在控制函数中直接return
我们想要返回的数据, 然后通过装饰器再接收并赋值给ctx.body
.
const methods = ['get', 'post', 'put', 'delete', 'options', 'head', 'patch'];
methods.forEach(method => {
router[method] = url => {
return function (target, name, descriptor) {
let fn = descriptor.value
descriptor.value = (router) => {
router[method](url, async(ctx, next) => {
const res = await fn(ctx, next); // 这里接收控制函数中返回值
if(res !== undefined){ // 默认情况下, 函数返回undefined, 因此这里需要过滤一下
ctx.body = res;
}
})
}
}
}
})
使用装饰器很简单, 新建一个class
为其加上router装饰器, 参数为koa-router
的config
const router = require('../utils/router'); // 导入上一步的装饰器
@router() // 装饰控制类
class HelloController {
@router.get('/get/:id') // 装饰方法
async hello(ctx){ // 定义控制函数
const {params, query, body} = ctx;
return ctx // 直接return需要返回的数据, 在上一步装饰器中接收
}
@router.post('/post')
async post(ctx){
return ctx.request.body;
}
}
module.exports = HelloController;
这里@router()
以及@router.get('/get/:id')
即我们上面步骤定义的类的装饰器.
然后我们在app.js
里面导入, 使用方式与koa-router
一样
import HelloController from './controller/hello';
// 添加的装饰器默认返回KoaRouter实例
app.use(HelloController.routes()).use(HelloController.allowedMethods());
- 本文代码托管在github.com.
- 参考: https://www.bougieblog.cn/
网友评论