在 Koa
应用中,通常会使用 koa-router
模块,提供对路由的支持。那为什么需要路由呢?做前后端分离开发的朋友都遇到过,对接接口的时候后台都会提供一个地址,请求这个地址,传相应参数就能实现相应地数据处理。你可以把这个接口理解为路由的地址。
1 koa-router 使用
1.1 安装引入
npm i koa-router --save
新建router.js
文件
const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router()
router.get('/', ctx => {
ctx.body = '你好,测不准'
})
// 注册路由
app.use(router.routes())
// 自动丰富 response 相应头,当未设置响应状态(status)的时候自动设置,在所有路由中间件最后设置(全局,推荐),也可以设置具体某一个路由(局部),例如:router.get('/index', router.allowedMethods()); 这相当于当访问 /index 时才设置
app.use(router.allowedMethods())
app.listen(3000, () => {
console.log('监听3000端口')
})
复制代码
增删改查的路由
// 获取列表,返回数组
router.get('/', ctx => {
ctx.body = []
})
// 根据id获取某一项,返回对象
router.get('/:id', ctx => {
ctx.body = {}
})
// 新建一项
router.post('/', ctx => {
ctx.body = {}
})
// 根据id更新某项
router.put('/:id', ctx => {
ctx.body = {}
})
// 根据id删除某一项
router.delete('/:id', ctx => {
ctx.body = {}
})
复制代码
koa-router
(支持基础的常用的http
方法('HEAD','OPTIONS','GET','PUT','PATCH','POST','DELETE'
),生僻的请求方法会返回501
状态码)
- 如果如要在路由前增加逻辑判断,例如是否有权限访问,可以在路由中插入中间件
// 定义中间件
const auth = async (ctx, next) => {
if (ctx.url !== '/users') {
ctx.throw(401)
}
await next()
}
// 注入
router.post('/users', auth, ctx => {
ctx.body = ctx.request.body
})
复制代码
1.2 参数获取
get
获取参数 (ctx.params
或者ctx.request.params
)
// 获取单个参数
router.get('/:id', ctx => {
ctx.body = ctx.params
})
// 获取多个参数
router.get('/:id/:age', ctx => {
ctx.body = ctx.request.params
})
复制代码
post
获取参数(ctx.request.body
) 获取参数需要用到第三方中间件,koa-bodyparser
:npm i koa-bodyparser --save
const Koa = require('koa')
const Router = require('koa-router')
const bodyParser = require('koa-bodyparser')
const app = new Koa()
const router = new Router()
router.post('/', ctx => {
ctx.body = ctx.request.body
})
// 放到路由前面
app.use(bodyParser())
app.use(router.routes())
app.use(router.allowedMethods())
app.listen(3000, () => {
console.log('监听3000端口')
})
复制代码
2 路由封装
我们开发项目不可能都把路由写在 index.js
入口文件中,需要使用单独的路由文件夹管理,只在入口文件引入一个即可。
- 根目录创建
app/routes/home.js
const Router = require('koa-router')
const router = new Router()
router.get('/', (ctx) => {
ctx.body = '首页'
})
module.exports = router
复制代码
- 创建
app/routes/users.js
const Router = require('koa-router')
// **配置路由前缀**
const router = new Router({
prefix: '/users'
})
router.get('/', (ctx) => {
ctx.body = '用户列表'
})
router.get('/:id', ctx => {
ctx.body = `用户id:${ctx.params.id}`
})
module.exports = router
复制代码
因为一个项目中的路由的接口会有很多,不可能在入口 index.js
中每个都要引入,所以需要对所有路由做个处理,这样在入口文件中只要导入一个即可
- 创建
app/routers/index.js
const fs = require('fs')
module.exports = app => {
// 读取当前目录下所有文件
fs.readdirSync(__dirname).forEach(file => {
// 除去归纳的 `index.js` 文件,其他的都要注册到 `app` 中
if (file === 'index.js') return
const router = require(`./${file}`)
app.use(router.routes()).use(router.allowedMethods())
})
}
复制代码
- 在
app/index.js
中引入
...
// 引入
const routing = require('./routes')
...
// 使用
routing(app)
...
复制代码
3 使用控制器
我们现在是把数据处理(虽然还没写数据,写死的哈)和路由放在一起,这样不便于维护处理,而且后面数据处理复杂了,会显得路由文件十分臃肿,我们应该把路由文件和数据处理分开来看。
- 创建
app/controllers/home.js
class HomeCtl {
index(ctx) {
ctx.body = '这是主页'
}
}
module.exports = new HomeCtl()
复制代码
- 修改
app/routes/home.js
const Router = require('koa-router')
const router = new Router()
//
const { index } = require('../controllers/home')
router.get('/', index)
module.exports = router
复制代码
- 创建
app/controllers/users.js
class UsersCtl {
async find(ctx) {
// 操作数据库一定要 await
ctx.body = '用户列表'
}
async findById(ctx) {
ctx.body = `用户id:${ctx.params.id}`
}
}
module.exports = new UsersCtl
复制代码
附:洋葱模型
通过前两节的介绍相信大家已经对 Koa
的执行机制有所了解,尤其是 await next()
执行完之后的中间件又会重新回来继续执行未执行的逻辑,不经感慨作者设计思路的巧妙。我们来简单了解下实现思路,核心代码在 koa-componse 中,代码也不多,我们简单学习下:
function compose(middleware) {
return function(context) {
// 我们其实只是返回了中间件的第一项,但是第一项执行过程中会递归执行后面的
return dispatch(0);
function dispatch(i) {
let fn = middleware[i];
try {
// 每次执行 fn 函数时候,fn 中包含 next 函数(dispatch(i + 1)),所以有了先执行 next 代码,再回过头来执行该中间件下面的代码
return Promise.resolve(fn(context, dispatch.bind(null, i + 1));
} catch (err) {
return Promise.reject(err);
}
}
};
}
复制代码
- 定义测试的中间件数组
const middleware = [
async (ctx, next) => {
console.log("1");
await next();
console.log("3");
},
async (ctx, next) => {
console.log("2");
}
];
复制代码
我们可以看到,在中间件中执行 await next()
,其实就是执行:await dispatch.bind(null, i + 1)
。因此看起来,当前中间件会停止自己的逻辑,先处理下一个中间件。因为每个 dispatch
,都返回新的 Promise
。所以 async
会等到 Promise
状态改变后再回来继续执行自己的逻辑。
- 简单写个按洋葱模型执行的方法(不考虑上线文,只是单纯的执行)
let middlewares = [
async (next) => {
console.log(1)
await next()
console.log(3)
},
async (next) => {
console.log(2)
await next()
},
async (next) => {
console.log('test')
}
]
function componse() {
return dispatch(0)
async function dispatch(i) {
let fn = middlewares[i]
await fn(dispatch.bind(null, i + 1))
}
}
componse()
复制代码
下一篇我们将学习数据库的操作,如果文章对你有帮助,欢迎关注呦!谢谢阅读!