全书目录

第三十六篇:Koa 完全指南 —— 洋葱关卡巡防宇宙

第四章:`ctx` —— 每一次巡防任务的总卷宗

2 分钟 567 字 第 687 / 962 个阅读单元

Koa 真正每天都在摸的核心对象,不是 app,而是 ctx

ctx 可以理解成这次巡防任务的总卷宗,上面挂着:

  • 请求信息
  • 响应信息
  • 当前应用实例
  • 中间件共享数据
  • 一堆对 HTTP 操作很顺手的快捷方法

先看一个例子:

js
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.methodctx.path 这些是常用请求快捷入口
  • ctx.get() 可以拿请求头
  • ctx.statusctx.body 直接控制响应

Koa 的设计很干净:

text
ctx
├── ctx.request    -> Koa 的请求对象
├── ctx.response   -> Koa 的响应对象
├── ctx.req        -> Node 原生 IncomingMessage
├── ctx.res        -> Node 原生 ServerResponse
├── ctx.state      -> 推荐的中间件共享数据区
└── ctx.app        -> 当前 Koa 应用实例

概念关系图可以直接记成这样:

text
Koa App
  │
  └── 每来一个请求
        │
        ▼
       ctx
   ┌────┼────┬────┬────┐
   ▼    ▼    ▼    ▼    ▼
request response state app raw req/res
#1. ctx.state 很重要

官方明确把 ctx.state 作为推荐的共享命名空间

js
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() 非常顺手
js
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.body
  • ctx.status
  • ctx.set()
  • ctx.type
  • ctx.redirect()

这才是 Koa 世界里的正路。