什么是Decoretor
- Decoretor是在
聲明階段
實(shí)現(xiàn)類與類的成員注解的一種語(yǔ)法, 其本質(zhì)就是一種特殊的函數(shù) - Decoretor有兩種使用方法, 一種是修飾類, 另一種是修飾類的成員, 其中, 類成員, 包括類屬性成員和類方法成員
- 如果一個(gè)類和類的方法都是用了Decorator权均,類方法的Decorator優(yōu)先于類的Decorator執(zhí)行
@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大致長(zhǎng)這樣, @log
是修飾類的裝飾器, @readonly
是修飾類的屬性的裝飾器, @validate
是修飾類的方法的裝飾器,
修飾類的裝飾器
修飾類的裝飾器, 其參數(shù)就是被修飾的類, 在上面例子中, @log
的方法接受的參數(shù)即Numberic
function log() {
const desc = Object.getOwnPropertyDescriptors(target.prototype);
for (const key of Object.keys(desc)) {
if(key === 'constructor'){
continue;
}
// 獲取屬性,
//這里需要注意, 在es6中, 類的屬性不一定是一個(gè)字符串常量,
const func = desc[key].value;
// 只關(guān)心類的方法
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
參數(shù)有三個(gè), 分別是類的實(shí)例對(duì)象, 屬性名稱, 以及該類成員的描述符
function readonly(target, key, desc){
// 我們只是希望該屬性不可修改, 因此只需要將描述符的writable屬性設(shè)為false
desc.writable = false;
}
修飾類方法的Decortor
類方法成員的裝飾器參數(shù)同類屬性的裝飾器一樣, 接受類的實(shí)例對(duì)象, 屬性名稱, 以及該類成員的描述符三個(gè)參數(shù)
function validate(target, key, desc){
const func = desc.value;
desc.value = function(...args){
// 遍歷入?yún)? for(let num of args){
if('number' !== typeof num){
throw new Error(`"${num}" is not a number`);
}
}
return func.apply(this, args);
}
}
帶參數(shù)的裝飾器
帶參數(shù)的裝飾器需在外層包裹一層函數(shù)接受參數(shù)
// Decorator不傳參
function Controller(target) {
}
// Decorator傳參
function Controller(params) {
return function (target) {
}
}
如果Decorator是傳參的腹缩,即使params有默認(rèn)值鹏氧,在調(diào)用時(shí)必須帶上括號(hào)尸折,即:
@Controller()
class Hello{
}
koa中用裝飾器描述應(yīng)用路由
先看看傳統(tǒng)的koa寫(xiě)法(不帶裝飾器)
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;
可以看出, 使用裝飾器之后的應(yīng)用路由更簡(jiǎn)潔,
- 首先我們先實(shí)現(xiàn)修飾controller類的裝飾器, 該裝飾器接受一個(gè)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) {
// 排除類的構(gòu)造方法
if (v !== 'constructor') {
let fn = functions[v].value
fn(router)
}
}
// 返回router實(shí)例, 供類成員的裝飾器使用
return router
}
}
接著, 我們?cè)賹?shí)現(xiàn)類的成員的裝飾器
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)
})
}
}
}
})
說(shuō)句題外話: 在koa
中, 我們通過(guò)ctx.body = xxx
來(lái)實(shí)現(xiàn)接口返回?cái)?shù)據(jù).
有了上面裝飾器, 我們可以在控制函數(shù)中直接return
我們想要返回的數(shù)據(jù), 然后通過(guò)裝飾器再接收并賦值給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); // 這里接收控制函數(shù)中返回值
if(res !== undefined){ // 默認(rèn)情況下, 函數(shù)返回undefined, 因此這里需要過(guò)濾一下
ctx.body = res;
}
})
}
}
}
})
使用裝飾器很簡(jiǎn)單, 新建一個(gè)class
為其加上router裝飾器, 參數(shù)為koa-router
的config
const router = require('../utils/router'); // 導(dǎo)入上一步的裝飾器
@router() // 裝飾控制類
class HelloController {
@router.get('/get/:id') // 裝飾方法
async hello(ctx){ // 定義控制函數(shù)
const {params, query, body} = ctx;
return ctx // 直接return需要返回的數(shù)據(jù), 在上一步裝飾器中接收
}
@router.post('/post')
async post(ctx){
return ctx.request.body;
}
}
module.exports = HelloController;
這里@router()
以及@router.get('/get/:id')
即我們上面步驟定義的類的裝飾器.
然后我們?cè)?code>app.js里面導(dǎo)入, 使用方式與koa-router
一樣
import HelloController from './controller/hello';
// 添加的裝飾器默認(rèn)返回KoaRouter實(shí)例
app.use(HelloController.routes()).use(HelloController.allowedMethods());
- 本文代碼托管在github.com.
- 參考: https://www.bougieblog.cn/