hooks

View source
通过 Nuxt 运行时 Hook 扩展 API 系统行为,实现全局请求拦截、错误处理和 401 自定义处理。

介绍

Movk Nuxt API 系统通过 Nuxt 运行时 Hook 暴露 4 个扩展点,可以在不修改源码的情况下扩展/定制 API 行为。

所有 Hook 类型已通过模块声明自动注册到 RuntimeNuxtHooks,编辑器中 nuxtApp.hook('movk:api:') 可获得完整自动补全。

可用 Hooks

movk:api:request

触发时机: 认证 token 注入完成后,请求实际发送前。

'movk:api:request': (context: FetchContext) => void | Promise<void>

典型用途:添加额外请求头、请求日志、修改请求参数。

plugins/api-logger.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('movk:api:request', (context) => {
    console.log(`[API] ${context.options.method || 'GET'} ${context.request}`)
  })
})

movk:api:response

触发时机: 业务状态码校验通过 + 数据解包完成后。

'movk:api:response': (
  context: FetchContext & { response: FetchResponse<any> }
) => void | Promise<void>

此时 context.response._data 已经是解包后的业务数据。

典型用途:全局数据转换、响应日志、性能分析。

plugins/api-analytics.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('movk:api:response', (context) => {
    analytics.track('api_success', {
      url: context.request,
      method: context.options.method || 'GET'
    })
  })
})

movk:api:error

触发时机: 业务状态码校验失败 HTTP 错误(4xx/5xx)。对于 401 错误,在 movk:api:unauthorized 之后触发。

'movk:api:error': (
  context: FetchContext & { response: FetchResponse<any> }
) => void | Promise<void>

典型用途:全局错误上报(Sentry 等)、错误分析。

plugins/error-reporter.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('movk:api:error', (context) => {
    // 上报到 Sentry(跳过已处理的 401)
    if (context.response.status !== 401) {
      Sentry.captureException(new Error(`API Error: ${context.response.status}`), {
        extra: {
          url: context.request,
          data: context.response._data
        }
      })
    }
  })
})

movk:api:unauthorized

触发时机: 收到 HTTP 401 响应时,在 movk:api:error 之前触发。

'movk:api:unauthorized': (
  context: FetchContext & { response: FetchResponse<any> },
  result: { handled: boolean }
) => void | Promise<void>

设置 result.handled = true跳过默认的 401 处理(清除 session + 跳转登录页)。

如果多个处理器监听此 Hook,任一处理器设置 result.handled = true 即可阻止默认行为。

Token 刷新模式

plugins/token-refresh.ts
export default defineNuxtPlugin((nuxtApp) => {
  let isRefreshing = false

  nuxtApp.hook('movk:api:unauthorized', async (_context, result) => {
    if (isRefreshing) return

    isRefreshing = true
    try {
      // 尝试刷新 token
      await $fetch('/api/auth/refresh', { method: 'POST' })
      // 刷新 session 状态
      const { fetch: fetchSession } = useUserSession()
      await fetchSession()
      // 标记已处理,跳过默认 401 行为
      result.handled = true
    }
    catch {
      // 刷新失败,走默认流程(清 session + 跳转登录)
    }
    finally {
      isRefreshing = false
    }
  })
})

注册方式

在 Nuxt 插件中(推荐)

适合全局逻辑,应用启动时自动注册:

plugins/api-hooks.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('movk:api:request', (context) => {
    // 全局请求处理
  })

  nuxtApp.hook('movk:api:error', (context) => {
    // 全局错误处理
  })
})

在组件中

适合特定页面的局部逻辑:

<script setup>
const nuxtApp = useNuxtApp()

// 仅在当前页面生效
const unregister = nuxtApp.hook('movk:api:error', (context) => {
  // 页面级别的错误处理
})

onUnmounted(() => {
  unregister() // 组件销毁时清理
})
</script>
在组件中注册 Hook 时,务必通过 onUnmounted 调用返回的卸载函数以避免内存泄漏。

执行顺序

请求发起
  │
  ├── 认证 token 注入
  ├── Debug 日志
  ├── movk:api:request             <-- 用户扩展点
  │
  ├── 网络请求发送 ─────────────>
  │
  ├── HTTP 2xx 响应
  │   ├── 业务成功 (code in successCodes)
  │   │   ├── 数据解包 (提取 dataKey)
  │   │   ├── movk:api:response    <-- 用户扩展点
  │   │   └── 成功 Toast
  │   │
  │   └── 业务失败
  │       ├── movk:api:error       <-- 用户扩展点
  │       ├── 错误 Toast
  │       └── throw ApiError
  │
  └── HTTP 4xx/5xx 响应
      ├── [401] movk:api:unauthorized  <-- 用户扩展点
      │       └── (未 handled) 清 session + 跳转登录
      ├── movk:api:error           <-- 用户扩展点
      └── 错误 Toast

TypeScript 支持

Hook 类型已通过模块声明自动注册:

src/runtime/types/module.ts
declare module 'nuxt/app' {
  interface RuntimeNuxtHooks {
    'movk:api:request': (context: FetchContext) => void | Promise<void>
    'movk:api:response': (context: FetchContext & { response: FetchResponse<any> }) => void | Promise<void>
    'movk:api:error': (context: FetchContext & { response: FetchResponse<any> }) => void | Promise<void>
    'movk:api:unauthorized': (
      context: FetchContext & { response: FetchResponse<any> },
      result: { handled: boolean }
    ) => void | Promise<void>
  }
}

无需额外配置,nuxtApp.hook('movk:api:...') 即可获得完整的类型推断和自动补全。

Copyright © 2025 - 2026 YiXuan - MIT License