Typed Context:如何用泛型定义 c.var.user: User?
const app = new Hono<{ Variables: { user: User } }>()
在 Hono 中,类型安全是构建可维护、可扩展应用的关键。虽然 c.var 提供了中间件间传递数据的能力,但默认情况下它是 any 类型,容易引发运行时错误。
通过 Hono 强大的 泛型上下文(Typed Context) 机制,你可以为 c.var、c.env、c.req.param 等添加精确的 TypeScript 类型,实现 编译时类型检查 和 IDE 智能提示。
一、问题:c.var.user 默认是 any
ts
// 没有类型安全
app.use('/api/*', async (c, next) => {
const user = { id: '123', name: 'John' }
c.set('user', user)
await next()
})
app.get('/profile', (c) => {
const user = c.var.user // → any
return c.json({ name: user.name.toUpperCase() })
})- 如果
user结构变化,编译器无法发现错误; - IDE 无法提供
.name、.id等属性提示。
二、解决方案:使用泛型定义 Variables
Hono 允许你在创建应用时,通过泛型参数定义上下文的类型结构:
ts
type User = {
id: string
name: string
email: string
}
// 定义 Typed Context
const app = new Hono<{
Variables: {
user: User
}
}>()现在 c.var.user 有了正确类型:
ts
app.use('/api/*', async (c, next) => {
const user: User = { id: '123', name: 'John', email: 'john@example.com' }
c.set('user', user) // ✅ 类型检查:必须是 User
await next()
})
app.get('/profile', (c) => {
const user = c.var.user // ✅ 类型为 User
// user. → IDE 自动提示 id, name, email
return c.json({ profile: user })
})如果误用:
ts
c.set('user', { wrong: 'data' }) // ❌ TS Error: 缺少 id, name, email三、扩展:同时定义 env 和 bindings
Hono 的泛型支持多个键,用于定义完整的上下文类型:
ts
type User = { id: string; name: string }
type Env = {
JWT_SECRET: string
DB: D1Database
ASSETS: Fetcher
}
const app = new Hono<{
Variables: {
user: User
role: 'admin' | 'user'
}
Bindings: Env // 对应 c.env
}>()现在所有 c.* 属性都有类型:
ts
app.use('/admin/*', async (c, next) => {
const token = c.req.header('Authorization')?.split(' ')[1]
const secret = c.env.JWT_SECRET // ✅ string
const db = c.env.DB // ✅ D1Database
if (!token) return c.json({ error: 'No token' }, 401)
try {
const payload = await verify(token, secret)
c.set('user', { id: payload.sub, name: payload.name })
c.set('role', 'admin')
await next()
} catch {
return c.json({ error: 'Invalid token' }, 401)
}
})
app.get('/admin/dashboard', (c) => {
const user = c.var.user // ✅ User
const role = c.var.role // ✅ 'admin' | 'user'
const db = c.env.DB // ✅ D1Database
return c.json({ admin: user, dbStatus: 'ok' })
})四、高级用法:路径参数类型化
你还可以为 c.req.param() 定义类型:
ts
// 定义路由参数类型
app.get('/users/:id/:role', (c) => {
const id = c.req.param('id') // string
const role = c.req.param('role') // string
// 但如果我们知道 role 只能是 'admin' | 'user'?
})结合泛型,可以更精确:
ts
const app = new Hono<{
Variables: { user: User }
Bindings: Env
'customParam': {
role: 'admin' | 'user'
}
}>()
app.get('/users/:id/:role', (c) => {
const id = c.req.param('id') // string
const role = c.req.param('role') // 'admin' | 'user' ✅
})注意:目前 Hono 对 param 的泛型支持有限,更推荐在函数内做类型断言或验证。
五、最佳实践:集中定义类型
创建 types.ts 统一管理:
ts
// types.ts
export type User = {
id: string
name: string
email: string
avatar?: string
}
export type Env = {
JWT_SECRET: string
DATABASE_URL: string
REDIS: Redis
BUCKET: R2Bucket
}
export type AppVariables = {
user: User
ipLocation?: { city: string; country: string }
}在应用中使用:
ts
// app.ts
import { Hono } from 'hono'
import type { AppVariables, Env } from './types'
const app = new Hono<{ Variables: AppVariables; Bindings: Env }>()六、为什么这很重要?
| 优势 | 说明 |
|---|---|
| 类型安全 | 避免 c.var.user.nam 拼写错误导致 undefined |
| IDE 提示 | .user. 后自动弹出可用属性 |
| 重构安全 | 修改 User 类型后,编译器提示所有使用点 |
| 文档化 | 类型即文档,新人快速理解上下文数据结构 |
| 减少 Bug | 编译时发现类型错误,而非运行时崩溃 |
七、总结
通过:
ts
const app = new Hono<{ Variables: { user: User } }>()你实现了:
c.var.user的类型为User;- 中间件注入和处理器读取都受类型检查;
- 配合
c.env泛型,构建全栈类型安全的 API。
Typed Context 是 Hono 类型系统的核心,它让你在享受轻量框架便利的同时,不失 TypeScript 的严谨性。在生产项目中,强烈建议从第一天就开始使用。