自定义 useFetch Nuxt

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

在使用 Nuxt 时,你可能正在构建前端并获取外部 API,并且你可能希望设置一些从 API 中获取数据的默认选项。

¥When working with Nuxt, you might be making the frontend and fetching an external API, and you might want to set some default options for fetching from your API.

$fetch 实用程序函数(由 useFetch 可组合项使用)特意设置为不可全局配置。这很重要,以便在整个应用中的获取行为保持一致,并且其他集成(如模块)可以依赖于核心实用程序(如 $fetch)的行为。

¥The $fetch utility function (used by the useFetch composable) is intentionally not globally configurable. This is important so that fetching behavior throughout your application remains consistent, and other integrations (like modules) can rely on the behavior of core utilities like $fetch.

但是,Nuxt 提供了一种为你的 API 创建自定义抓取器的方法(如果你要调用多个 API,则可以创建多个抓取器)。

¥However, Nuxt provides a way to create a custom fetcher for your API (or multiple fetchers if you have multiple APIs to call).

自定义 $fetch

¥Custom $fetch

让我们创建一个带有 Nuxt 插件 的自定义 $fetch 实例。

¥Let's create a custom $fetch instance with a Nuxt plugin.

$fetchofetch 的一个已配置实例,它支持添加 Nuxt 服务器的基 URL,以及在服务器端渲染 (SSR) 期间直接调用函数(避免 HTTP 往返)。

我们假设:

¥Let's pretend here that:

  • 主要 API 是 https://api.nuxt.com
  • 我们将 JWT 令牌存储在与 nuxt-auth-utils 的会话中。
  • 如果 API 响应 401 状态码,我们会将用户重定向到 /login 页面。
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) {
        // note that this relies on ofetch >= 1.4.0 - you may need to refresh your lockfile
        options.headers.set('Authorization', `Bearer ${session.value?.token}`)
      }
    },
    async onResponseError({ response }) {
      if (response.status === 401) {
        await nuxtApp.runWithContext(() => navigateTo('/login'))
      }
    }
  })

  // Expose to useNuxtApp().$api
  return {
    provide: {
      api
    }
  }
})

使用此 Nuxt 插件,$api 可从 useNuxtApp() 公开,以便直接从 Vue 组件进行 API 调用:

¥With this Nuxt plugin, $api is exposed from useNuxtApp() to make API calls directly from the Vue components:

app.vue
<script setup>
const { $api } = useNuxtApp()
const { data: modules } = await useAsyncData('modules', () => $api('/modules'))
</script>
使用 useAsyncData 封装可避免在进行服务器端渲染(服务器和客户端混合渲染)时重复获取数据。¥Wrapping with useAsyncDataavoid double data fetching when doing server-side rendering (server & client on hydration).

自定义 useFetch/useAsyncData

¥Custom useFetch/useAsyncData

既然 $api 已经具备了我们想要的逻辑,让我们创建一个 useAPI 可组合组件来替换 useAsyncData + $api 的用法:

¥Now that $api has the logic we want, let's create a useAPI composable to replace the usage of 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
  })
}

让我们使用新的可组合项,创建一个简洁美观的组件:

¥Let's use the new composable and have a nice and clean component:

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

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

¥If you want to customize the type of any error returned, you can also do so:

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。