许多面对象言语中都有装潢器(Decorator)函数的观点,Javascript言语的ES7范例中也说起了Decorator,个人认为装潢器是和async/await
一样让人高兴的的变化。正如其“装潢器”的叫法所表达的,他能够对一些对象举行装潢包装然后返回一个被包装过的对象,能够装潢的对象包括:类,属性,要领等。
Node.js现在已支撑了async/await
语法,但decorator
还须要babel的插件支撑,细致的设置不在叙说。(停止发稿时候2018-12-29)
下面是援用的关于decorator
语法的一个示例:
@testable
class Person {
@readonly
@nonenumerable
name() { return `${this.first} ${this.last}` }
}
从上面代码中,我们一眼就可以看出,Person
类是可测试的,而name
要领是只读和不可枚举的。
关于 Decorator 的细致引见拜见下面两篇文章:
希冀结果
关于Node.js中的路由,人人应当都很熟习了,无论是在自身写的http/https
效劳中,照样在Express
、Koa
等框架中。我们要为路由供应要求的URL
和其他须要的GET
及POST
等参数,随后路由须要依据这些数据来实行响应的代码。
关于Decorator和路由的连系我们此次愿望写出相似下面的代码:
@Controller('/tags')
export default class TagRouter {
@Get(':/id')
@Login
@admin(['developer', 'adminWebsite'])
@require(['phone', 'password'])
@Log
async getTagDetail(ctx, next) {
//...
}
}
关于这段代码的诠释:
第一行,经由过程
Controller
装潢
TagRouter
类,为类下的路由函数增加一致途径前缀
/tags
。第二行,建立并导出
TagRouter
类。第三行,经由过程装潢器为
getTagDetail
要领增加途径和要求要领。第四行,经由过程装潢器限定提议要求须要用户登录。
第五行,经由过程装潢器限定提议要求的用户必需具有开发者或许网站管理员权限。
第六行,经由过程装潢器搜检要求参数必需包括
phone
和
password
字段。第七行,经由过程装潢器为要求打印log。
第八行,路由真正实行的要领。
如许不仅简化、范例化了路由的写法,减少了代码的冗余和毛病,还使代码寄义一览无余,无需解释也能通俗易懂,便于保护、交代等事件。
细致完成
下面就着手写一个关于movies
的路由细致实例,示例采纳koa2
+ koa-router
为基础组织代码。
文件途径:/server/routers/movies.js
import mongoose from 'mongoose';
import { Controller, Get, Log } from '../decorator/router';
import { getAllMovies, getSingleMovie, getRelativeMovies } from '../service/movie';
@Controller('/movies')
export default class MovieRouter {
@Get('/all')
@Log
async getMovieList(ctx, next) {
const type = ctx.query.type;
const year = ctx.query.year;
const movies = await getAllMovies(type, year);
ctx.body = {
data: movies,
success: true,
};
}
@Get('/detail/:id')
@Log
async getMovieDetail(ctx, next) {
const id = ctx.params.id;
const movie = await getSingleMovie(id);
const relativeMovies = await getRelativeMovies(movie);
ctx.body = {
data: {
movie,
relativeMovies,
},
success: true,
}
}
}
代码中Controller
为路由增加一致前缀,Get
指定要求要领和途径,Log
打印日记,参考上面的预期示例。
关于
mongodb
以及猎取数据的代码这里就不贴出了,毕竟只是示例罢了,人人能够依据自身的资本,自行修改成自身的逻辑。
重点我们看一下,GET /movies/all
以及GET /movies//detail/:id
这两个路由的装潢器完成。
文件途径:/server/decorator/router.js
import KoaRouter from 'koa-router';
import { resolve } from 'path';
import glob from 'glob'; // 运用shell情势婚配文件
export class Route {
constructor(app, routesPath) {
this.app = app;
this.router = new KoaRouter();
this.routesPath = routesPath;
}
init = () => {
const {app, router, routesPath} = this;
glob.sync(resolve(routesPath, './*.js')).forEach(require);
// 细致处置惩罚逻辑
app.use(router.routes());
app.use(router.allowedMethods());
}
};
- 起首,导出一个
Route
类,供应给外部运用,Route
类的组织函数吸收两个参数app
和routesPath
,app
即为koa2
实例,routesPath
为路由文件途径,如上面movies.js
的routesPath
为/server/routers/
。 - 然后,供应一个初始化函数
init
,初始化逻辑中。援用一切routesPath
下的路由,并use
路由实例。
如许的话我们就可以够在外部如许挪用Route类:
import {Route} from '../decorator/router';
import {resolve} from 'path';
export const router = (app) => {
const routesPath = resolve(__dirname, '../routes');
const instance = new Route(app, routesPath);
instance.init();
}
好了,基础框架搭好了,来看细致逻辑的完成。
先补充完init要领:
文件途径:/server/decorator/router.js
const pathPrefix = Symbol('pathPrefix');
init = () => {
const {app, router, routesPath} = this;
glob.sync(resolve(routesPath, './*.js')).forEach(require);
R.forEach( // R为'ramda'要领库,相似'lodash'
({target, method, path, callback}) => {
const prefix = resolvePath(target[pathPrefix]);
router[method](prefix + path, ...callback);
}
)(routeMap)
app.use(router.routes());
app.use(router.allowedMethods());
}
为了加载路由,须要一个路由列表routeMap
,然后遍历routeMap
,挂载路由,init
事情就完成了。
下边的重点就是向routeMap
中塞入数据,这里每一个路由对象采纳object
的情势有四个key
,分别为target
, method
, path
, callback
。
target
即为装潢器函数的
target
(这里重要为了猎取路由途径的前缀),
method
为要求要领,
path
为要求途径,
callback
为要求实行的函数。
下边是设置路由途径前缀和塞入routeMap
内容的装潢器函数:
export const Controller = path => (target, key, descriptor) => {
target.prototype[pathPrefix] = path;
return descriptor;
}
export const setRouter = method => path => (target, key, descriptor) => {
routeMap.push({
target,
method,
path: resolvePath(path),
callback: changeToArr(target[key]),
});
return descriptor;
}
-
Controller
就不多说了,就是挂载前缀途径到类的原型对象上,这里须要注重的是Controller
作用于类,所以target
是被润饰的类自身。 -
setRouter
函数也很简朴把接受到的途径格式化处置惩罚,把路由处置惩罚函数包装成数组,以后与target
、method
一同组织城对象塞入routeMap
。
这里有两个辅佐函数,简朴贴下代码看下:
import R from 'ramda'; // 相似'lodash'的要领库
// 假如途径是以/开首直接返回,不然补充/后返回
const resolvePath = R.unless(
R.startsWith('/'),
R.curryN(2, R.concat)('/'),
);
// 假如参数是函数直接返回,不然包装成数组返回
const changeToArr = R.unless(
R.is(Array),
R.of,
);
接下来是get
、post
、put
、delete
要领的细致完成,实在就是挪用setRouter
就好了:
export const Get = setRouter('get');
export const Post = setRouter('post');
export const Put = setRouter('put');
export const Delete = setRouter('delete');
至此,重要的功用就悉数完成了,接下来是一些辅佐Decorator,人人能够参考和运用core-decorators.js,它是一个第三方模块,供应了几个罕见的润饰器,经由过程它也能够更好地明白润饰器。
下面以Log
为示例,完成一个辅佐Decorator,其他Decorator人人自身发挥:
let logTimes = 0;
export const convert = middleware => (target, key, descriptor) => {
target[key] = R.compose(
R.concat(
changeToArr(middleware)
),
changeToArr,
)(target[key]);
return descriptor;
}
export const Log = convert(async (ctx, next) => {
logTimes++;
console.time(`${logTimes}: ${ctx.method} - ${ctx.url}`);
await next();
console.timeEnd(`${logTimes}: ${ctx.method} - ${ctx.url}`);
})
convert
是一个辅佐函数,起首把一般函数转换成数组,然后跟其他中间件函数兼并。此辅佐函数也可用于其他辅佐Decorator。
好了,到此文章就完毕了,人人多交换,本人github
下一篇:分享koa2源码解读
末了贴出症结的/server/decorator/router.js的完全代码
import R from 'ramda';
import KoaRouter from 'koa-router';
import glob from 'glob';
import {resolve} from 'path';
const pathPrefix = Symbol('pathPrefix')
const routeMap = [];
let logTimes = 0;
const resolvePath = R.unless(
R.startsWith('/'),
R.curryN(2, R.concat)('/'),
);
const changeToArr = R.unless(
R.is(Array),
R.of,
);
export class Route {
constructor(app, routesPath) {
this.app = app;
this.router = new KoaRouter();
this.routesPath = routesPath;
}
init = () => {
const {app, router, routesPath} = this;
glob.sync(resolve(routesPath, './*.js')).forEach(require);
R.forEach(
({target, method, path, callback}) => {
const prefix = resolvePath(target[pathPrefix]);
router[method](prefix + path, ...callback);
}
)(routeMap)
app.use(router.routes());
app.use(router.allowedMethods());
}
};
export const Controller = path => (target, key, descriptor) => {
console.log(target);
target.prototype[pathPrefix] = path;
return descriptor;
}
export const setRouter = method => path => (target, key, descriptor) => {
console.log('setRouter');
routeMap.push({
target,
method,
path: resolvePath(path),
callback: changeToArr(target[key]),
});
return descriptor;
}
export const Get = setRouter('get');
export const Post = setRouter('post');
export const Put = setRouter('put');
export const Delete = setRouter('delete');
export const convert = middleware => (target, key, descriptor) => {
target[key] = R.compose(
R.concat(
changeToArr(middleware)
),
changeToArr,
)(target[key]);
return descriptor;
}
export const Log = convert(async (ctx, next) => {
logTimes++;
console.time(`${logTimes}: ${ctx.method} - ${ctx.url}`);
await next();
console.timeEnd(`${logTimes}: ${ctx.method} - ${ctx.url}`);
})