Context 上下文
🎯 引言
学完这篇文章,你能说清楚 ctx 到底是什么、里面装了什么,会用 ctx.query、ctx.body、ctx.status 这些常用别名获取请求信息、设置响应内容,这是写 Koa 代码每天都要打交道的基础功。
🧱 ctx 是什么
在原生 http 和 Express 里,请求信息和响应信息分成 req、res 两个对象。Koa 把它们俩封装进了一个对象,这个对象就是 ctx(Context,上下文)。
可以把 ctx 想象成快递员的配送单:一张单子上既写着“收件人信息”(请求方发来的数据),也留着“回执栏”(我们要返回的响应)。处理一次请求,从头到尾只需要拿着这一张单子,不用在两个对象之间来回切换。
每个请求进来,Koa 都会为它创建一个独立的 ctx,请求之间互不影响。中间件的写法还记得吗:
app.use(async (ctx) => {
// ctx 就是这次请求的“配送单”
});
🧱 ctx.request 与 ctx.response
ctx 内部又把信息分成两块:
ctx.request:请求相关信息,比如请求地址、请求方法、查询参数。ctx.response:响应相关信息,比如返回内容、状态码、响应头。
看个例子:
app.use(async (ctx) => {
console.log(ctx.request.path); // 请求路径,如 /users
console.log(ctx.request.method); // 请求方法,如 GET
ctx.response.body = '你好'; // 设置响应内容
ctx.response.status = 200; // 设置状态码
});
但这样写有点啰嗦。Koa 提供了别名机制,把高频属性直接挂到 ctx 上,让我们少敲几个字。
💡 常用别名速查
别名是实际开发中的主流写法,最常用的有这些:
| 别名 | 等价于 | 作用 |
|---|---|---|
ctx.query | ctx.request.query | 查询参数对象 |
ctx.path | ctx.request.path | 请求路径 |
ctx.method | ctx.request.method | 请求方法 |
ctx.body | ctx.response.body | 读取/设置响应内容 |
ctx.status | ctx.response.status | 读取/设置状态码 |
ctx.set() | ctx.response.set() | 设置响应头 |
综合起来用一次:
app.use(async (ctx) => {
// 访问 /search?keyword=koa 时
const keyword = ctx.query.keyword; // 拿到查询参数 koa
ctx.set('X-Powered-By', 'Koa'); // 自定义响应头
ctx.status = 200;
ctx.body = {
message: `你搜索的是:${keyword}`,
path: ctx.path,
method: ctx.method,
};
});
浏览器访问 http://localhost:3000/search?keyword=koa,就能收到一段包含搜索词的 JSON 响应。注意查询参数不需要自己解析 ?keyword=koa 这段字符串,ctx.query 直接给的是解析好的对象。
ctx.xxx 读取,回响应数据给 ctx.body、ctx.status 赋值。这套别名能覆盖日常开发的大部分场景。⚠️ 原生 req 和 res 还能用吗
能。Koa 底层仍然基于 Node.js 的 http 模块,原生对象挂在 ctx.req 和 ctx.res 上:
app.use(async (ctx) => {
console.log(ctx.req.url); // 原生请求对象的 url
});
但不建议混用。Koa 封装好的 ctx 已经能处理几乎所有场景,直接用原生对象操作响应(比如 ctx.res.end())会绕过 Koa 的响应处理流程,容易引发响应状态混乱。
ctx.body 设置,不要去碰 ctx.res.end()。只有极少数底层场景(如特殊的流处理)才需要动原生对象。🧾 小节总结
ctx是 Koa 为每个请求创建的上下文对象,把请求和响应封装在一起,请求之间互相独立。ctx.request存请求信息,ctx.response存响应信息。- 高频属性都有
ctx上的别名:ctx.query、ctx.path、ctx.method、ctx.body、ctx.status、ctx.set()。 - 查询参数通过
ctx.query直接拿到解析好的对象,不需要手动解析。 - 原生
ctx.req、ctx.res仍然存在,但不要混用,响应统一走ctx.body。
❓ 知识问答
Q1:ctx.query 拿到的参数是什么类型?
都是字符串。比如访问 ?page=2,ctx.query.page 是字符串 '2' 而不是数字 2。需要数字时用 Number() 转一下。
Q2:同一个查询参数传了多次怎么办?
比如 ?tag=a&tag=b,ctx.query.tag 会是数组 ['a', 'b']。只传一次时是字符串,处理前注意判断类型。
Q3:ctx.body 可以读吗?
可以。在后续中间件里能读到前面中间件设置的值,不过实际开发中“读 body”的场景很少,一般都是最后设置一次。
Q4:ctx.status 不设置会怎样?
默认是 404。但只要给 ctx.body 赋了值,Koa 会自动把状态码改成 200。所以一般不需要手动设置 200。
🧪 小练习
- 写一个接口
/user,接收name和age两个查询参数,返回 JSON:{ "greeting": "你好,xxx,今年 xx 岁" }。
import Koa from 'koa';
const app = new Koa();
app.use(async (ctx) => {
if (ctx.path === '/user') {
// 请在这里编写代码
}
});
app.listen(3000);
- 用浏览器访问
http://localhost:3000/user?name=小明&age=18验证结果。
🎉 恭喜你已经掌握了 ctx 的常用操作!下一篇我们挑战 Koa 的精华:中间件与洋葱模型,搞懂它你就真正理解 Koa 了。
