我正在参加「掘金·启航计划」
前言
我本身是一个前端,工作和平时学习中更多的是跟前端的框架打交道。但是随着技术的提升,要不断的往技术的深度和广度进行深挖。曾经多次学习过node以及他的技术栈express,koa,但是因为写的少,总是忘记,没有到达一个熟练掌握的状态。希望通过此文,能牢靠掌握koa2,并像全栈进发。
express和koa2区别,为什么我要选择koa2?
相同点:
- 建立在http模块基础上:底层都是建立在
node.js内置的http模块上。http模块生成服务器的原始代码如下
var http = require("http");
var app = http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.end("Hello world!");
});
app.listen(3000, "localhost");
不同点
- 中间件的处理方式不同:express是一个中间件处理完,再传递给下一个中间件。而koa使用的是洋葱模型。
- koa更加轻量:express其自带 Router、路由规则、View 等功能。而koa把这些都交给中间件进行处理
- koa使用了async,await:express处理异步的方式主要是callback,其源码也是callback太多。而koa使用了async,await处理异步,代码更清晰易懂,维护更方便。
通过上面的对比发现,koa的特点是更加轻量,速度更快,代码更清晰易懂,自由度高,所以我毫不犹豫的选择koa
基本用法
const Koa = require("koa");
const app = new Koa();
app.use(ctx => { //处理请求的中间件
ctx.response.body = "hello world";
})
app.listen(3000);
我们可以这么联想koa是一个继承了eventEmitter的类,这个类主要有两个函数,use和listen
为什么node中的fs,http,net等内置模块都继承了EventEmitter类呢?
- 解决了“回调地狱”
- 将多个模块进行了解耦,自己执行时,不需要知道另一个模块的存在,只需要关心发布出来的事件就行
- 因为多个模块可以不知道对方的存在,自己关心的事件可能是一个很遥远的旮旯发布出来的,也不能通过代码跳转直接找到发布事件的地方,debug的时候可能会有点困难。
use函数的作用是什么?
use函数的作用很简单,就是存储中间件函数的,use中的函数就是中间件函数
listen函数的作用是什么?
listen函数也很简单,就是上面说的对http模块的一个封装
listen(...args) {
// 这里的this.callback也就是执行中间件的时候
const server = http.createServer(this.callback);
server.listen(...args);
}
context的作用是什么?
我们注意一下中间件函数中的第一个参数ctx,也就是context
Koa提供一个Context对象,表示一次对话的上下文(包括HTTP请求和HTTP回复)。通过加工这个对象,就可以控制返回给用户的内容
ctx.response代表HTTP Response。同样地,ctx.request代表HTTP Request
什么是中间件
中间件的本质就是一个函数,在收到请求和返回相应的过程中做一些我们想做的事情。Express文档中对它的作用是这么描述的:
"中间件"(middleware),它处在 HTTP Request 和 HTTP Response 中间,用来实现某种中间功能。app.use()用来加载中间件
- 每个中间件默认接受两个参数,第一个参数是
Context对象,第二个参数是next函数。只要调用next函数,就可以把执行权转交给下一个中间件
常用的中间件包括路由级中间件,错误处理中间件,应用级中间件
洋葱模型是如何执行的
- 最外层的中间件首先执行。
- 调用next函数,把执行权交给下一个中间件。
- ...
- 最内层的中间件最后执行。
- 执行结束后,把执行权交回上一层的中间件。
- ...
- 最外层的中间件收回执行权之后,执行next函数后面的代码
compose源码
// 异步递归遍历函数
// 传入中间件数组,返回一个函数
// 默认只执行第一个中间件,如果第一个中间件不执行next()方法的话,那么不会执行第二个中间件,依次类推
// 注意要用promise.resolve包裹,这样每一个中间件的返回值都是一个promise了,所以可以使用await next()
compose(middleware) {
return function (context) {
const dispatch = (i) => {
let fn = middleware[i];
if (i === middleware.length) {
return Promise.resolve();
}
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
};
// 执行第一个中间件
return dispatch(0);
};
}
异步中间件
const fs = require('fs.promised');
const Koa = require('koa');
const app = new Koa();
const main = async function (ctx, next) {
ctx.response.type = 'html';
ctx.response.body = await fs.readFile('./demos/template.html', 'utf8');
};
app.use(main);
app.listen(3000);
路由的使用
koa-route 模块 原生路由用起来不太方便,我们可以使用封装好的
koa-route模块
const Koa = require('koa')
const fs = require('fs')
const app = new Koa()
const Router = require('koa-router')
let home = new Router()
// 子路由1
home.get('/', async ( ctx )=>{
let html = `
<ul>
<li><a href="/page/helloworld">/page/helloworld</a></li>
<li><a href="/page/404">/page/404</a></li>
</ul>
`
ctx.body = html
})
// 子路由2
let page = new Router()
page.get('/404', async ( ctx )=>{
ctx.body = '404 page!'
}).get('/helloworld', async ( ctx )=>{
ctx.body = 'helloworld page!'
})
// 装载所有子路由
let router = new Router()
router.use('/', home.routes(), home.allowedMethods())
router.use('/page', page.routes(), page.allowedMethods())
// 加载路由中间件
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000, () => {
console.log('[demo] route-use-middleware is starting at port 3000')
})
静态资源
如果网站提供静态资源(图片、字体、样式表、脚本......),为它们一个个写路由就很麻烦,也没必要。koa-static模块封装了这部分的请求
// 访问 http://localhost:3000/test.json
const Koa = require("koa");
const app = new Koa();
const path = require('path');
const serve = require('koa-static');
const main = serve(path.join(__dirname, "../public/"));
app.use(main);
app.listen(3000);
有时候也可以在静态文件中设置html,这样会根据路由去找对应的index.html
返回html文件
home.get('/', async (ctx) => {
const index = path.join(__dirname, './index.html');
// 设置response的Content-Type:
ctx.response.type = 'html'; // 关键是这里,设置返回类型
ctx.response.body = fs.readFileSync(index);
})