搭建一个node-koa2全栈开发环境。
koa1.0 koa2.0 express 区别在哪?
koa1.0 generator可以把异步代码写成同步的样子。es6
koa2.0 promise和async解决异步。es7
入门:基本的koa使用
1 | // 导入koa,和koa 1.x不同,在koa2中,我们导入的是一个class,因此用大写的Koa表示: |
很简单。就是ctx中间件封装了request和response模块。
处理URL
假如我们要调用不同的接口,返回不同的内容。koa如何才能做这样的拦截。
很简单。就是request模块中有path。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21app.use(async (ctx, next) => {
if (ctx.request.path === '/') {
ctx.response.body = 'index page';
} else {
await next();
}
});
app.use(async (ctx, next) => {
if (ctx.request.path === '/test') {
ctx.response.body = 'TEST page';
} else {
await next();
}
});
app.use(async (ctx, next) => {
if (ctx.request.path === '/error') {
ctx.response.body = 'ERROR page';
} else {
await next();
}
});
这样写感觉代码太过于复杂。
引入koa-router
利用中间间处理url映射。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21const Koa = require('koa');
// 注意require('koa-router')返回的是函数:
const router = require('koa-router')();
const app = new Koa();
// log request URL:
app.use(async (ctx, next) => {
console.log(`Process ${ctx.request.method} ${ctx.request.url}...`);
await next();
});
// add url-route:
router.get('/hello/:name', async (ctx, next) => {
var name = ctx.params.name;
ctx.response.body = `<h1>Hello, ${name}!</h1>`;
});
router.get('/', async (ctx, next) => {
ctx.response.body = '<h1>Index</h1>';
});
// add router middleware:
app.use(router.routes());
app.listen(3000);
console.log('app started at port 3000...');
这样写好多了。
但是这样写几乎框架没有可用性。越写越复杂化。
因为没有人会想在写完一个模块的时候,第二天找不到这个文件在哪了,更不说假如二次开发会带来什么后果。
所有的URL处理函数都放到app.js里显得很乱,而且,每加一个URL,就需要修改app.js。随着URL越来越多,app.js就会越来越长。
为了解决耦合度,必须通过解耦合的方式来处理。
于是,我们分层。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17url2-koa/
|
+- .vscode/
| |
| +- launch.json <-- VSCode 配置文件
|
+- controllers/
| |
| +- login.js <-- 处理login相关URL
| |
| +- users.js <-- 处理用户管理相关URL
|
+- app.js <-- 使用koa的js
|
+- package.json <-- 项目描述文件
|
+- node_modules/ <-- npm安装的所有依赖包
我们把app作为一个加载所有资源的文件。把他加载到koa框架中去。只要这个进程存在,用户访问时的路径就会被拦截下来。
注册url
1 | function addMapping(router, mapping) { |
上面已经做了代码简化。
全栈目录结构
1 | view-koa/ |
再弄个页面文件就好啦。
MVC
调用ctx.render(view, model)完成页面输出。
如何才能做真实的登录访问呢。首先,登录后返回cookie给前端页面。
往后每次调用一个后端参数,都需要带上cookie做登录访问。
bootstrap
1 | view-koa/ |
查找范围文件的中间件.并返回body中
1 | const path = require('path'); |
集成Nunjucks
集成Nunjucks实际上也是编写一个middleware,这个middleware的作用是给ctx对象绑定一个render(view, model)的方法,这样,后面的Controller就可以调用这个方法来渲染模板了。
我们创建一个templating.js来实现这个middleware:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38const nunjucks = require('nunjucks');
function createEnv(path, opts) {
var
autoescape = opts.autoescape === undefined ? true : opts.autoescape,
noCache = opts.noCache || false,
watch = opts.watch || false,
throwOnUndefined = opts.throwOnUndefined || false,
env = new nunjucks.Environment(
new nunjucks.FileSystemLoader(path || 'views', {
noCache: noCache,
watch: watch,
}), {
autoescape: autoescape,
throwOnUndefined: throwOnUndefined
});
if (opts.filters) {
for (var f in opts.filters) {
env.addFilter(f, opts.filters[f]);
}
}
return env;
}
function templating(path, opts) {
// 创建Nunjucks的env对象:
var env = createEnv(path, opts);
return async (ctx, next) => {
// 给ctx绑定render函数:
ctx.render = function (view, model) {
// 把render后的内容赋值给response.body:
ctx.response.body = env.render(view, Object.assign({}, ctx.state || {}, model || {}));
// 设置Content-Type:
ctx.response.type = 'text/html';
};
// 继续处理请求:
await next();
};
}
module.exports = templating;
app.js 处理过的中间件。加载过程如下
1 | 第一个middleware是记录URL以及页面执行时间: |