Context 上下文
Context 提供请求级别的全局状态,就像 React Hooks 一样。
什么是 Context?
Context 提供请求级别的全局状态,就像 React Hooks 一样:
typescript
import { createContext } from 'farrow-pipeline'
// 创建上下文
const UserContext = createContext<User | null>(null)
const RequestIdContext = createContext<string>('')
const DBContext = createContext<Database | null>(null)
// Context API:
UserContext.get() // 获取当前值
UserContext.set(user) // 设置值
UserContext.assert() // 断言非空 (抛出异常如果为空)基础用法
创建和使用 Context
typescript
import { createContext } from 'farrow-pipeline'
interface User {
id: string
name: string
email: string
}
// 创建用户上下文
const UserContext = createContext<User | null>(null)
// 在中间件中设置
app.use((req, next) => {
const token = req.headers?.authorization
if (token) {
const user = validateToken(token)
UserContext.set(user)
}
return next(req)
})
// 在路由中获取
app.get('/profile').use(() => {
const user = UserContext.get()
if (!user) {
return Response.status(401).json({ error: 'Unauthorized' })
}
return Response.json({ user })
})断言非空
typescript
// 断言用户存在(如果为 null 会抛出异常)
app.get('/protected').use(() => {
const user = UserContext.assert() // 如果为 null,抛出异常
return Response.json({ userId: user.id })
})上下文隔离
重要: 每个 HTTP 请求都有独立的上下文容器,互不干扰!
typescript
const CounterContext = createContext(0)
app.use((req, next) => {
const count = CounterContext.get()
CounterContext.set(count + 1)
return next(req)
})
app.get('/count').use(() => {
const count = CounterContext.get()
return Response.json({ count })
})
// 并发请求测试
await Promise.all([
fetch('/count'), // count: 1
fetch('/count'), // count: 1
fetch('/count') // count: 1
])
// 每个请求都独立,互不影响!隔离演示
typescript
const UserContext = createContext<string>('匿名')
const app = Http()
app.use((req, next) => {
// 从 header 中获取用户名
const username = req.headers['x-user-name'] || '匿名'
UserContext.set(username)
return next(req)
})
app.get('/whoami').use(() => {
const username = UserContext.get()
return Response.json({ username })
})
// 并发请求,每个都有独立的上下文
fetch('/whoami', { headers: { 'X-User-Name': 'Alice' } }) // { username: "Alice" }
fetch('/whoami', { headers: { 'X-User-Name': 'Bob' } }) // { username: "Bob" }
fetch('/whoami') // { username: "匿名" }多上下文协作
typescript
import { createContext } from 'farrow-pipeline'
interface User {
id: string
name: string
}
interface Logger {
log: (msg: string) => void
error: (msg: string) => void
}
interface Database {
query: (sql: string) => Promise<any>
}
// 创建多个上下文
const UserContext = createContext<User | null>(null)
const RequestIdContext = createContext<string>('')
const LoggerContext = createContext<Logger>(console)
const DBContext = createContext<Database | null>(null)
// 初始化所有上下文
app.use((req, next) => {
// 设置请求 ID
const requestId = generateRequestId()
RequestIdContext.set(requestId)
// 设置日志器
const logger = createLogger({ requestId })
LoggerContext.set(logger)
// 设置数据库连接
const db = getDatabaseConnection()
DBContext.set(db)
// 认证用户
const token = req.headers?.authorization
if (token) {
const user = validateToken(token)
UserContext.set(user)
}
logger.log(`Request started: ${req.method} ${req.pathname}`)
const response = next(req)
logger.log(`Request completed: ${response.info.status?.code}`)
return response
})
// 使用多个上下文
app.get('/profile').use(async () => {
const user = UserContext.assert()
const logger = LoggerContext.get()
const db = DBContext.assert()
logger.log(`User ${user.id} accessing profile`)
const profile = await db.query(
'SELECT * FROM profiles WHERE user_id = ?',
[user.id]
)
return Response.json({ profile })
})Context Hooks
内置 Hooks
typescript
import { useRequestInfo, useBasenames, usePrefix } from 'farrow-http'
app.use(() => {
// 获取请求信息
const req = useRequestInfo()
console.log(req.pathname, req.method, req.query)
// 获取基础路径列表
const basenames = useBasenames() // ['/api', '/v1']
// 获取完整路径前缀
const prefix = usePrefix() // '/api/v1'
return Response.json({ basenames, prefix })
})自定义 Hooks
typescript
// 创建自定义 Hook
function useUser() {
const user = UserContext.get()
if (!user) {
throw new Error('用户未登录')
}
return user
}
function useLogger() {
return LoggerContext.get()
}
function useDB() {
const db = DBContext.get()
if (!db) {
throw new Error('数据库未连接')
}
return db
}
// 使用
app.get('/api/posts').use(async () => {
const user = useUser() // 自动断言用户存在
const logger = useLogger()
const db = useDB() // 自动断言数据库存在
logger.log(`User ${user.id} fetching posts`)
const posts = await db.query('SELECT * FROM posts WHERE author_id = ?', [user.id])
return Response.json({ posts })
})实战场景
场景 1: 请求追踪
typescript
interface TraceInfo {
traceId: string
startTime: number
steps: Array<{ name: string; timestamp: number }>
}
const TraceContext = createContext<TraceInfo>({
traceId: '',
startTime: 0,
steps: []
})
// 初始化追踪
app.use((req, next) => {
TraceContext.set({
traceId: generateTraceId(),
startTime: Date.now(),
steps: []
})
return next(req)
})
// 添加追踪步骤
function addTraceStep(name: string) {
const trace = TraceContext.get()
trace.steps.push({
name,
timestamp: Date.now() - trace.startTime
})
}
// 使用
app.get('/api/data').use(async () => {
addTraceStep('开始处理')
const data = await fetchData()
addTraceStep('数据获取完成')
const processed = processData(data)
addTraceStep('数据处理完成')
const trace = TraceContext.get()
return Response.json({
data: processed,
trace: {
id: trace.traceId,
duration: Date.now() - trace.startTime,
steps: trace.steps
}
})
})场景 2: 数据库事务
typescript
const TransactionContext = createContext<Transaction | null>(null)
// 事务中间件
const withTransaction = async (req, next) => {
const db = DBContext.assert()
const transaction = await db.beginTransaction()
TransactionContext.set(transaction)
try {
const response = await next(req)
if (response.info.status?.code < 400) {
await transaction.commit()
} else {
await transaction.rollback()
}
return response
} catch (error) {
await transaction.rollback()
throw error
}
}
// 使用
app.post('/api/transfer', { body: TransferInput })
.use(withTransaction)
.use(async (req) => {
const transaction = TransactionContext.assert()
const { fromAccount, toAccount, amount } = req.body
await transaction.query(
'UPDATE accounts SET balance = balance - ? WHERE id = ?',
[amount, fromAccount]
)
await transaction.query(
'UPDATE accounts SET balance = balance + ? WHERE id = ?',
[amount, toAccount]
)
return Response.json({ success: true })
})场景 3: 多语言支持
typescript
type Language = 'zh-CN' | 'en-US'
const LanguageContext = createContext<Language>('zh-CN')
const translations = {
'zh-CN': {
'welcome': '欢迎',
'goodbye': '再见'
},
'en-US': {
'welcome': 'Welcome',
'goodbye': 'Goodbye'
}
}
// 检测语言
app.use((req, next) => {
const acceptLanguage = req.headers['accept-language'] || 'zh-CN'
const language = acceptLanguage.startsWith('zh') ? 'zh-CN' : 'en-US'
LanguageContext.set(language)
return next(req)
})
// 翻译函数
function t(key: string): string {
const language = LanguageContext.get()
return translations[language][key] || key
}
// 使用
app.get('/').use(() => {
return Response.json({
message: t('welcome')
})
})场景 4: 权限控制
typescript
type Permission = 'read' | 'write' | 'admin'
interface User {
id: string
name: string
permissions: Permission[]
}
const UserContext = createContext<User | null>(null)
// 权限检查函数
function requirePermission(...requiredPermissions: Permission[]) {
return (req, next) => {
const user = UserContext.assert()
const hasPermission = requiredPermissions.some(p =>
user.permissions.includes(p)
)
if (!hasPermission) {
return Response.status(403).json({
error: 'Insufficient permissions'
})
}
return next(req)
}
}
// 使用
app.get('/api/users').use(
requirePermission('read', 'admin'),
() => {
return Response.json({ users: getAllUsers() })
}
)
app.delete('/api/users/<id:int>').use(
requirePermission('admin'),
(req) => {
deleteUser(req.params.id)
return Response.status(204).empty()
}
)最佳实践
✅ 适合用 Context
- 🟢 跨中间件共享的请求级数据(用户、请求 ID)
- 🟢 需要隔离的状态
- 🟢 数据库连接、日志器等资源
typescript
// ✅ 好:适合用 Context
const UserContext = createContext<User | null>(null)
const RequestIdContext = createContext<string>('')
const DBContext = createContext<Database | null>(null)❌ 不适合用 Context
- 🔴 全局配置(用模块变量)
- 🔴 可以通过参数传递的数据
- 🔴 不需要隔离的状态
typescript
// ❌ 不必要:用模块变量就行
const ConfigContext = createContext({ apiUrl: 'https://api.example.com' })
// 改成这样:
const API_URL = 'https://api.example.com'命名规范
typescript
// ✅ 好:以 Context 结尾
const UserContext = createContext<User | null>(null)
const LoggerContext = createContext<Logger>(console)
// ❌ 不好:名称不清晰
const User = createContext<User | null>(null)
const log = createContext<Logger>(console)类型安全
typescript
// ✅ 好:明确指定类型
const UserContext = createContext<User | null>(null)
// ❌ 不好:依赖类型推导
const UserContext = createContext(null) // 类型是 null合理的默认值
typescript
// ✅ 好:提供合理的默认值
const LoggerContext = createContext<Logger>(console)
const ConfigContext = createContext({ debug: false })
// ⚠️ 注意:null 作为默认值时要处理
const UserContext = createContext<User | null>(null)
app.use(() => {
const user = UserContext.get()
if (!user) {
return Response.status(401).json({ error: 'Unauthorized' })
}
// 使用 user
})