Koa 真正每天都在摸的核心对象,不是 app,而是 ctx。
ctx 可以理解成这次巡防任务的总卷宗,上面挂着:
- 请求信息
- 响应信息
- 当前应用实例
- 中间件共享数据
- 一堆对 HTTP 操作很顺手的快捷方法
先看一个例子:
app.use(async (ctx) => {
const method = ctx.method
const path = ctx.path
const query = ctx.query
const auth = ctx.get('Authorization')
ctx.status = 200
ctx.body = {
method,
path,
query,
auth: auth || null,
}
})这里你能直接感受到:
ctx.method、ctx.path这些是常用请求快捷入口ctx.get()可以拿请求头ctx.status、ctx.body直接控制响应
Koa 的设计很干净:
ctx
├── ctx.request -> Koa 的请求对象
├── ctx.response -> Koa 的响应对象
├── ctx.req -> Node 原生 IncomingMessage
├── ctx.res -> Node 原生 ServerResponse
├── ctx.state -> 推荐的中间件共享数据区
└── ctx.app -> 当前 Koa 应用实例概念关系图可以直接记成这样:
Koa App
│
└── 每来一个请求
│
▼
ctx
┌────┼────┬────┬────┐
▼ ▼ ▼ ▼ ▼
request response state app raw req/res#1. ctx.state 很重要
官方明确把 ctx.state 作为推荐的共享命名空间。
app.use(async (ctx, next) => {
ctx.state.requestId = crypto.randomUUID()
ctx.state.user = { id: 'u_36', role: 'captain' }
await next()
})
app.use(async (ctx) => {
ctx.body = {
requestId: ctx.state.requestId,
user: ctx.state.user,
}
})这比你到处往 ctx 上乱挂字段更稳。
#2. ctx.throw() 和 ctx.assert() 非常顺手
app.use(async (ctx) => {
const token = ctx.get('Authorization')
ctx.assert(token, 401, '缺少令牌')
ctx.body = { ok: true }
})ctx.throw() / ctx.assert() 的意义是:
- 把 HTTP 错误语义直接写进业务代码
- 自动带上状态码
- 用户级错误会带
err.expose
#3. 少碰原始 res.write() / res.end()
Koa 官方文档明确提醒:
不要绕开 Koa 的响应处理去乱写原始 res。
也就是说,大多数时候你就老老实实用:
ctx.bodyctx.statusctx.set()ctx.typectx.redirect()
这才是 Koa 世界里的正路。