Koa学习笔记 了解Koa和创建路由实例

1,575 阅读5分钟

通过对 14nodejs(7天) 的学习,已经对 Node.js 略有了解

终于尝试着逐渐接触自己一直想做而未达成的目标 微信公众订阅号开发

当然,以自己目前的水平,依然要依托教程的帮助,这里要学习的课程是 7天搞定Node.js微信公众号开发

这是自己结识 Node.js 以来的第二个7天的课程,但在之前还要对教程中涉及的 Koa 框架进行学习


Koa 介绍

koa 是由 Express 原班人马打造的,一个基于 Node.js 平台的 Web 开发框架

Express 是基于 ES5 的语法,随着新版 Node.js 开始支持 ES6 ,该团队重新编写诞生了 koa 1.0 ,而 koa2.0 则是更是超前的基于 ES7

安装 koa2

npm i koa

创建 koa2 工程

// 导入koa,和koa 1.x不同,在koa2中,我们导入的是一个class,因此用大写的Koa表示:
const Koa = require('koa');

// 创建一个Koa对象表示web app本身:
const app = new Koa();

app.use( ... )

// 在端口3000监听:
app.listen(3000);

app.use([path,] function [, function…])

在 path 上安装中间件,每当请求的路径的基路径和该path匹配时,都会导致该中间件函数被执行,如果 path 没有被设定,那么默认为 /

app.use('/admin', function(req, res) {
   console.log(req.originalUrl); // '/admin/new'
	console.log(req.baseUrl); // '/admin'
	console.log(req.path); // '/new'
});
app.use(async (ctx, next) => {
    await next(); // 处理下一个异步函数    
    ctx.response.type = 'text/html' // 设置 response 的 Content-Type
    ctx.response.body = '<h1>Hello, koa2!</h1>' // 设置 response 的内容
})

async 标记的函数称为异步函数,在异步函数中,可以用 await 调用另一个异步函数,这两个关键字将在 ES7 中引入(用法看上去类似 Promise)

上述代码中,参数 ctx 是由 koa 传入的封装了 requestresponse 的变量,我们可以通过它访问 requestresponsenext 是 koa 传入的将要处理的下一个异步函数

对于每一个 http 请求, koa 将调用通过 app.use() 注册的 async 函数,并传入 ctxnext 参数

实例:

const Koa=require('koa');
const app=new Koa();
app.use(async (ctx, next) => {
    console.log('第一1');
    await next(); // 调用下一个middleware
    console.log('第一2');
});
app.use(async (ctx, next) => {
    console.log('第二1');
    await next(); // 调用下一个middleware
    console.log('第二2');
});
app.use(async (ctx, next) => {
    console.log('第三1');
    await next();
    console.log('第三2');
});
app.listen(3000);

// 打印结果如下:
// 第一1
// 第二1
// 第三1
// 第三2
// 第二2
// 第一2

koa-router 和 koa-bodyparser

koa-router 负责处理 URL 映射,通过它可以更便捷的对 URL 进行操作

koa-router 使用

const router = require('koa-router')(); // 引入 koa-router
……
// add router middleware:
app.use(router.routes());

注意导入 koa-router 的语句最后的 () 是对函数调用

通过使用 koa-router ,我们可以处理一些更复杂的 URL 操作,比如 GET 请求

// log request URL:
app.use(async (ctx, next) => {
    console.log(`<h1>Hello, ${name}!</h1>`);
    await next();
});

// add url-route:
router.get('/hello/:name', async (ctx, next) => {
    var name = ctx.params.name; // 通过 ctx.params.name 可以获取 URL 带来的变量
    ctx.response.body = `<h1>Hello, ${name}!</h1>`;
});

处理 get 请求

router.get('/path', async fn) 处理的是 get 请求

处理 post 请求

router.post('/path', async fn) 处理 post 请求

需要注意,post 请求的数据,无法正确为客户端解析成正确的 request 的 body ,此时需要利用 koa-bodyparser 组件实现解析

koa-bodyparser 使用

const router = require('koa-router')(); // 引入 koa-router
const bodyParser = require('koa-bodyparser');
app.use(bodyParser()); // 在 router.routes 之前注入 bodyParser
……
router.post('/path', async fn)
……
// add router middleware:
app.use(router.routes());
app.listen(3000);

注意 koa-bodyparser 必须在 router 之前被注册到 app 对象上

路由实例

提取路由

为了使目录结构更为美观,可以将所有的 URL 处理函数都提取出来

// index.js
var fn_index = async (ctx, next) => { …… };
var fn_signin = async (ctx, next) => { …… };
module.exports = {
    'GET /': fn_index,
    'POST /signin': fn_signin
};

app.js 页面在获取上,廖雪峰 的实例代码略显复杂,如下

var fs = require('fs') // 引入 Node.js 核心模块 fs

// 通过 fs.readdirSync 同步读取 /controllers 目录下的文件
// 该源实例 index.js 存放于 /controllers 目录下
var files = fs.readdirSync(__dirname + '/controllers');
// 源实例注解:这里可以用sync是因为启动时只运行一次,不存在性能问题

// 过滤该目录下所有 .js 后缀的文件
var js_files = files.filter((f)=>{
    return f.endsWith('.js');
});

// 循环所有 .js 后缀的文件
for (var f of js_files) {
	// 动态获取到 /controllers 目录下最后一个 以 .js 为结尾的导出集合
    let mapping = require(__dirname + '/controllers/' + f);
    for (var url in mapping) { // 此时的 mapping 为每一个 .js 文件的 exports 对象,循环该对象
        if (url.startsWith('GET ')) { // 如果对象键名 url 已 'GET ' 开头
            var path = url.substring(4); // 提取键名中的路径部分,此处是 '/'
            router.get(path, mapping[url]);
        } else if (url.startsWith('POST ')) { // 如果对象键名 url 已 'POST ' 开头
            var path = url.substring(5); // 提取键名中的路径部分,此处是 '/signin'
            router.post(path, mapping[url]);
        } else {
            // 无效的URL:
            console.log(`invalid URL: ${url}`);
        }
    }
}

fs.readdirSync(path[, options]) 同步版本的 fs.readdir() ,参数如下

  • path String | Buffer | URL
  • options String | Object
    • encoding String 默认值 'utf8'
    • withFileTypes Boolean 默认值 false
  • 返回 <string[]> | <Buffer[]>| <fs.Dirent[]>

可选的 options 参数可以是指定编码的字符串,也可以是具有 encoding 属性的对象,该属性指定用于传给回调的文件名的字符编码。 如果 encoding 设置为 'buffer',则返回的文件名是 Buffer 对象。

__dirname

在每个 Node.js 模块中,除 require 、exports 等模块之外还都包含两个特殊成员

  • __dirname 动态获取当前文件模块所属目录的绝对路径
  • __filename 动态获取当前文件的绝对路径

str.startsWith(searchString [, position])

startsWith() 方法用来判断当前字符串是否是以另外一个给定的子字符串开头的,根据判断结果返回 true 或 false

  • searchString 要搜索的子字符串
  • position 在 str 中搜索 searchString 的开始位置,默认值为 0,也就是真正的字符串开头处

endsWith()

方法用于测试字符串是否以指定的后缀结束。如果参数表示的字符序列是此对象表示的字符序列的后缀,则返回 true,否则返回 false

stringObject.substring(start,stop)

substring() 方法用于提取字符串中介于两个指定下标之间的字符

  • start 必需。一个非负的整数,规定要提取的子串的第一个字符在 stringObject 中的位置

  • stop 可选。一个非负的整数,比要提取的子串的最后一个字符在 stringObject 中的位置多 1。

    如果省略该参数,那么返回的子串会一直到字符串的结尾

精简入口文件(app.js)

将以上代码复制一份,命名为 controller.js ,优化代码,如下

var fs = require('fs') // 引入 Node.js 核心模块 fs
const router = require('koa-router')(); // 引入 koa-router
// 函数 addMapping 包含两个参数
// router koa-router 实例
// mapping 所有路由 url
function addMapping(router, mapping) {
    for (var url in mapping) { // 此时的 mapping 为每一个 .js 文件的 exports 对象,循环该对象
        if (url.startsWith('GET ')) { // 如果对象键名 url 已 'GET ' 开头
            var path = url.substring(4); // 提取键名中的路径部分,此处是 '/'
            router.get(path, mapping[url]);
        } else if (url.startsWith('POST ')) { // 如果对象键名 url 已 'POST ' 开头
            var path = url.substring(5); // 提取键名中的路径部分,此处是 '/signin'
            router.post(path, mapping[url]);
        } else {
            console.log(`invalid URL: ${url}`);
        }
    }
}
function addControllers(router, dir) {
    // 通过 fs.readdirSync 同步读取 /controllers 目录下的文件
	// 该源实例 index.js 存放于 /controllers 目录下
    var files = fs.readdirSync(__dirname + '/' + dir);
    // 过滤该目录下所有 .js 后缀的文件
    var js_files = files.filter((f) => {
        return f.endsWith('.js');
    });
    // 循环所有 .js 后缀的文件
    for (var f of js_files) {
    	// 动态获取到 /controllers 目录下最后一个 以 .js 为结尾的导出集合
        let mapping = require(__dirname + '/' + dir + '/' + f);
        // 调用 addMapping 函数,并将
        addMapping(router, mapping);
    }
}
module.exports = function (dir) {
    let controllers_dir = dir || 'controllers', // 如果不传参数,扫描目录默认为'controllers'
        router = require('koa-router')(); // 动态引入 koa-router 组件
    addControllers(router, controllers_dir);
    // 实参 router 为实例化 router
    // 实参 controllers_dir 为路径
    return router.routes(); // add router middleware
};

这样一来,我们在 app.js 的代码又简化了

// 导入 controller middleware:
const controller = require('./controller');

// 使用 middleware:
app.use(controller());

文章已同步我的个人博客:《Node学习笔记 初识Koa


资料参考:

本文由博客一文多发平台 OpenWrite 发布!