Skip to content

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
})

这是一个第三方 Farrow 文档站 | 用 ❤️ 和 TypeScript 构建