Koa 2入门
koa2已发布了一段时间,可以考虑入手,参见Node.js最新Web技术栈(2016年4月)
本文主要是koa 2的文档解读和runkoa介绍,让大家对koa 2有一个更简单直接的理解
依赖Node.js 4.0+
Koa requires node v4.0.0 or higher for (partial) ES2015 support.
部分特性需要ES2015,大家可以自己比对一下es6在node不同版本里的支持特性
http://kangax.github.io/compat-table/es6/
hello world
const Koa = require('koa');
const app = new Koa();
// 此处开始堆叠各种中间件
//...
app.use(ctx => {
ctx.body = 'Hello Koa';
});
app.listen(3000);
注意注释部分,此处开始堆叠各种中间件
中间件:Middleware
Koa 是一个 middleware framework, 它提供了 3 种不同类型的中间件写法
- common function
- async function(新增)
- generatorFunction
中间件和express的中间件类似,是有顺序的,注意,大部分人都坑死在顺序上
下面以写一个logger中间件为例,一一阐明
最常见的写法
node sdk就支持的,就是最常见的
app.js
const Koa = require('koa');
const app = new Koa();
app.use((ctx, next) => {
const start = new Date();
return next().then(() => {
const ms = new Date() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
});
// response
app.use(ctx => {
ctx.body = 'Hello Koa in app.js';
});
app.listen(3000);
async/await 函数 (Babel required)
async/await是异步流程控制更好的解决方案,很多潮人都已经玩起来了,目前node sdk不支持,所以需要babel来转换一下
app-async.js
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
const start = new Date();
await next();
const ms = new Date() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
// response
app.use(ctx => {
ctx.body = 'Hello Koa in app-async.js';
});
app.listen(3000);
Generator函数
Generator是node 4(严格是0.12)开始支持的es6特性里的非常重要的一个,用generator和promise实现流程控制,让co充当执行器这一个角色,也是个不错的解决方案
千万别把generator叫成生成器,我们一般习惯把scaffold叫成生成器
app-generator.js
const Koa = require('koa');
const app = new Koa();
const co = require('co');
app.use(co.wrap(function *(ctx, next) {
const start = new Date();
yield next();
const ms = new Date() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
}));
// response
app.use(ctx => {
ctx.body = 'Hello Koa in app-generator.js';
});
app.listen(3000);
测试
启动执行
npm i -g runkoa
runkoa app.js
runkoa app-async.js
runkoa app-generator.js
测试发起 http 请求
$ curl http://127.0.0.1:3000
Hello Koa in app.js
v3将移除单纯的以generator作为中间件的写法
Old signature middleware (v1.x) support will be removed in v3
实际是koa核心包含了一个叫koa-convert的模块,它里面warning说,以generator作为中间件的写法将在koa@3里不支持
但是用co或koa-convert转过的还是可以的,本文的3种写法都是长期支持的
这样写不行。。。。
// Koa will convert
app.use(function *(next) {
const start = new Date();
yield next;
const ms = new Date() - start;
console.log(`${this.method} ${this.url} - ${ms}ms`);
});
这样写是可以的
const convert = require('koa-convert');
app.use(convert(function *(next) {
const start = new Date();
yield next;
const ms = new Date() - start;
console.log(`${this.method} ${this.url} - ${ms}ms`);
}));
Babel支持
我本人比较讨厌写babel,对于node sdk不支持的特性持观望态度,比如async/await这样的神器是可以用的,其他的是不一定一定要上的,那就观望好了
如果在koa 2里用到async/await就需要babel支持了
- es2015-node5(nodejs 5.x里支持的es6特性)
- stage-3(包含async/await)
可是,我还是不想用,就几行代码能搞定的事儿,我不想看到babel出现在我的代码里,于是就有了前面用到的runkoa,它的原理也是这样的,不过看起来更clean一些
总结
Node.js 4.x和5.x支持的es特性还是有很大差异的,如果不用到,还好,万一用到就只能babel去转换,还有就是async支持,必须要stage-3,那么也还是需要babel。
Node.js sdk迟迟不更新很讨厌,babel更新太快也很讨厌
但是,无论从性能,还是流程控制上,koa 2和它的后宫(中间件)都是非常好的解决方案
全文完
欢迎关注我的公众号【node全栈】
联系我,更多交流
下一篇,补充express和koa的区别。。。。好多人问这个
mark~~
router第一种写法
app.use(require('./routers').routes());
第二种写法
const index = require('./routers/index.js');
const users = require('./routers/users.js');
router.use('/', index.routes(), index.allowedMethods());
router.use('/users', users.routes(), users.allowedMethods());
routers目录下存储路由 routers/users.js
var router = require('koa-router')();
router.get('/list', function (ctx, next) {
ctx.body = 'this a users list!';
});
module.exports = router;
两种写法是不是一样 第一种写法 http://localhost:3000/list 对应路由 routers/users.js 第二种写法 http://localhost:3000/users/list 对应路由 routers/users.js
@yakczh 这个在koa-generator里就是这样做的,这里主要是科普koa2的3种中间件写法,哈哈,还没到路由层面呢
koa2貌似已经可以用到生产环境了,但是koa2貌似还是不支持jade,难道要强行改回去用ejs么?
@WangCao 支持jade啊,koa-views
太感谢了,mark
最后一种写法 就是 koa-convert
的那种,为何 next不加括号 而其他的几种next都是加上括号的
@i5ting 赞赞赞,看koa-jade还不支持,原来还有koa-views这种render各种template的东西!!
generator 不叫做生成器,那该翻译成什么好
@gdut-zdc 就叫generator好了,这是es6的,这样更好理解。生成器容易理解成生成某个东西的东东。。。
请问runkoa支持es6的全部特性吗?
@dazhihappy 不是呀,只支持async函数
const start = new Date();
return next().then(() => {
const ms = new Date() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
这里罗嗦了,可以
console.time(`${ctx.method} ${ctx.url}`);
return next().then(() => {
console.timeEnd(`${ctx.method} ${ctx.url}`);
});
@andyhu 感谢指正,O(∩_∩)O谢谢啊
@i5ting 哥,能请问koa-views怎么使用handlebars么?不知道layout和partial怎么配置。。
@jsann 我不确定可以不以这样用,hbs功能有限,koa里推荐jade和nunjucks
const handlebars = require('koa-handlebars'); // handlebars templating
// handlebars templating
app.use(handlebars({
extension: ['html', 'handlebars'],
viewsDir: 'apps/admin',
partialsDir: 'apps/admin/templates/partials',
}));
或者https://github.com/gilt/koa-hbs,这个支持layout
@i5ting 谢谢你的回复,这两个好像不支持koa2吧,所以我才用的koa-views,但是不知道怎么配置。。
@jsann convert一下吧,如果用koa-views换jade和nunjucks吧
@i5ting 那也只能去转一下了,谢谢啦~~
@jsann 我也在找koa-views的handlebars来的。累了我大半夜。其实都不知道koa2的es6式gen用法,天真地以为像express嘞,看到狼叔的17koa/koa-generator才恍然大悟。 看源码吧,koa-views@5.1.2/lib/index.js:46行,通过consolidate加载view引擎. consolidate/lib/index.js:714开始是加的Handlebars的support. 基本就两行,registerPartial和registerHelper.
那,我的配置如下:
koa.use(views(‘views’, {
extension: ‘hbs’, // 文件后缀
map: { hbs: handlebars }, //看源码知道,是上面的字符串为key, 寻找相应的view引擎的映射
options: {
defaultLayout: “default”, //没有用,本以为可以加载个默认layout
helpers: {//registerHelper时读这里。这里可以放layout的, 有个handlebars-layouts就是这么做的。
uppercase: (str) => str.toUpperCase()
},
partials: {//registerPartials时读这。相对于views的路径去找。
before: ‘…/partials/before’,
after: ‘…/partials/after’,
header: ‘…/partials/header’,
footer: ‘…/partials/footer’,
}
}
}));
@palmtale 我也遇到了,koa-hbs那个模块只支持koa1,至今未解决!
准备切换到2了
金刚狼老师,我入坑了,调试的时候了V3(PS:v3是什么鬼不再支持generator,表示一脸懵逼。有木有什么koa的交流群。我要入群学习哈哈~
路由还可以这么写哦~
controller/hello.js
let fn_hello_a = async (ctx, next) => {
let name = ctx.params.name;
ctx.response.body = `<h1>Hello, A!</h1>`;
};
let fn_hello_b = async (ctx, next) => {
let name = ctx.params.name;
ctx.response.body = `<h1>Hello, B!</h1>`;
};
let fn_hello = async (ctx, next) => {
let name = ctx.params.name;
ctx.response.body = `<h1>Hello, ${name}!</h1>`;
};
// GET /hello/:name': fn_hello
module.exports = [
{
method : 'GET',
path : '/hello/a',
handle : fn_hello_a,
},
{
method : 'GET',
path : '/hello/b',
handle : fn_hello_b,
},
{
method : 'GET',
path : '/hello/:name',
handle : fn_hello,
},
{
method : 'GET',
path : '/hello/:name',
handle : fn_hello,
},
];
app.js
fs.readdirSync(__dirname + '/controllers').filter( (f) => {
return f.endsWith('.js');
}).map( (f) => {
return require(__dirname + '/controllers/' + f)
}).forEach( (list)=>{
list.map( (obj) => {
router[obj.method](obj.path,obj.handle);
});
});