Nuxt 中的自定义 useFetch

如何在 Nuxt 中创建调用外部 API 的自定义 fetcher。

在使用 Nuxt 时,你可能正在构建前端并调用外部 API,或许你想为调用 API 设置一些默认的 fetch 选项。

$fetch 工具函数(useFetch 组合式函数内部使用)故意不允许全局配置。这一点很重要,因为它能够保证整个应用中的 fetch 行为保持一致,并且其他集成(如模块)可以依赖 $fetch 这种核心工具的行为。

不过,Nuxt 提供了一种方法,允许你为你的 API 创建自定义的 fetcher(如果有多个 API 需要调用,也可以创建多个 fetcher)。

自定义 $fetch

让我们使用 Nuxt 插件 来创建一个自定义的 $fetch 实例。

$fetch 是配置过的 ofetch 实例,支持自动添加你的 Nuxt 服务器的基础 URL,并且在 SSR 阶段直接进行函数调用,从而避免 HTTP 网络往返。

假设:

plugins/api.ts
export default defineNuxtPlugin((nuxtApp) => {
  const { session } = useUserSession()

  const api = $fetch.create({
    baseURL: 'https://api.nuxt.com',
    onRequest({ request, options, error }) {
      if (session.value?.token) {
        // 注意:这依赖于 ofetch >= 1.4.0 - 你可能需要刷新锁文件
        options.headers.set('Authorization', `Bearer ${session.value?.token}`)
      }
    },
    async onResponseError({ response }) {
      if (response.status === 401) {
        await nuxtApp.runWithContext(() => navigateTo('/login'))
      }
    }
  })

  // 通过 useNuxtApp().$api 暴露
  return {
    provide: {
      api
    }
  }
})

利用这个 Nuxt 插件,$api 会在 useNuxtApp() 中暴露,方便你直接在 Vue 组件里调用 API:

app.vue
<script setup>
const { $api } = useNuxtApp()
const { data: modules } = await useAsyncData('modules', () => $api('/modules'))
</script>
使用 useAsyncData避免服务端渲染时数据被双重请求(服务器端及客户端 hydration 阶段)。

自定义 useFetch/useAsyncData

既然 $api 已包含了我们期望的逻辑,让我们构建一个 useAPI 组合式函数,用来替代原本 useAsyncData + $api 的用法:

composables/useAPI.ts
import type { UseFetchOptions } from 'nuxt/app'

export function useAPI<T>(
  url: string | (() => string),
  options?: UseFetchOptions<T>,
) {
  return useFetch(url, {
    ...options,
    $fetch: useNuxtApp().$api as typeof $fetch
  })
}

我们现在可以使用这个新组合式函数,编写更加整洁的组件:

app.vue
<script setup>
const { data: modules } = await useAPI('/modules')
</script>

如果你想自定义返回错误的类型,也可以这么操作:

import type { FetchError } from 'ofetch'
import type { UseFetchOptions } from 'nuxt/app'

interface CustomError {
  message: string
  statusCode: number
}

export function useAPI<T>(
  url: string | (() => string),
  options?: UseFetchOptions<T>,
) {
  return useFetch<T, FetchError<CustomError>>(url, {
    ...options,
    $fetch: useNuxtApp().$api
  })
}
此示例展示了如何使用自定义的 useFetch,而自定义 useAsyncData 的结构完全相同。
我们目前正在讨论更简洁的方式来允许你创建自定义 fetcher,详见:https://github.com/nuxt/nuxt/issues/14736。