資料獲取

Nuxt 提供了可組合函式來處理應用程式中的資料獲取。

Nuxt 提供了兩個可組合函式和一個內建庫,用於在瀏覽器或伺服器環境中執行資料獲取:useFetchuseAsyncData$fetch

簡而言之

  • $fetch 是發出網路請求最簡單的方法。
  • useFetch$fetch 的一個封裝,它在通用渲染中只獲取一次資料。
  • useAsyncData 類似於 useFetch,但提供了更精細的控制。

無論是 useFetch 還是 useAsyncData 都有一套共同的選項和模式,我們將在最後幾節中詳細介紹。

useFetchuseAsyncData 的需求

Nuxt 是一個可以在伺服器和客戶端環境中運行同構(或通用)程式碼的框架。如果在 Vue 元件的 setup 函式中使用 $fetch 函式執行資料獲取,這可能會導致資料被獲取兩次,一次在伺服器上(用於渲染 HTML),另一次在客戶端(當 HTML 被水合時)。這可能會導致水合問題,增加互動時間並導致不可預測的行為。

useFetchuseAsyncData 可組合函式透過確保如果 API 呼叫發生在伺服器上,資料會以有效負載的形式轉發到客戶端來解決此問題。

有效負載是一個 JavaScript 物件,可以透過 useNuxtApp().payload 訪問。它在客戶端使用,以避免在程式碼在瀏覽器水合期間執行時重複獲取相同的資料。

使用Nuxt DevToolsPayload tab 中檢查這些資料。
app/app.vue
<script setup lang="ts">
const { data } = await useFetch('/api/data')

async function handleFormSubmit () {
  const res = await $fetch('/api/submit', {
    method: 'POST',
    body: {
      // My form data
    },
  })
}
</script>

<template>
  <div v-if="data == undefined">
    No data
  </div>
  <div v-else>
    <form @submit="handleFormSubmit">
      <!-- form input tags -->
    </form>
  </div>
</template>

在上面的示例中,useFetch 將確保請求發生在伺服器中,並正確轉發到瀏覽器。$fetch 沒有這種機制,當請求完全由瀏覽器發出時,它是一個更好的選擇。

Suspense

Nuxt 在底層使用 Vue 的<Suspense>元件,以防止在所有非同步資料都可用之前進行導航。資料獲取可組合函式可以幫助您利用此功能,並根據每次呼叫使用最適合的方法。

您可以新增 <NuxtLoadingIndicator> 以在頁面導航之間新增進度條。

$fetch

Nuxt 包含了ofetch庫,並自動匯入為您的應用程式中全域性的 $fetch 別名。

pages/todos.vue
<script setup lang="ts">
async function addTodo () {
  const todo = await $fetch('/api/todos', {
    method: 'POST',
    body: {
      // My todo data
    },
  })
}
</script>
請注意,僅使用 $fetch 不會提供網路呼叫去重和導航阻止
建議將 $fetch 用於客戶端互動(基於事件)或與 useAsyncData 結合使用,用於獲取初始元件資料。
閱讀更多關於 $fetch 的資訊。

將客戶端頭傳送到 API

在伺服器上呼叫 useFetch 時,Nuxt 將使用 useRequestFetch 來代理客戶端頭和 cookie(不應轉發的頭除外,例如 host)。

<script setup lang="ts">
const { data } = await useFetch('/api/echo')
</script>
// /api/echo.ts
export default defineEventHandler(event => parseCookies(event))

另外,下面的示例展示瞭如何使用 useRequestHeaders 從伺服器端請求(源自客戶端)訪問 cookie 並將其傳送到 API。透過使用同構的 $fetch 呼叫,我們確保 API 端點可以訪問使用者瀏覽器最初發送的相同 cookie 頭。這僅在您不使用 useFetch 時才需要。

<script setup lang="ts">
const headers = useRequestHeaders(['cookie'])

async function getCurrentUser () {
  return await $fetch('/api/me', { headers })
}
</script>
您還可以使用 useRequestFetch 自動將標頭代理到呼叫。
在將標頭代理到外部 API 之前,請務必謹慎,並且只包含您需要的標頭。並非所有標頭都可以安全地繞過,並且可能會引入意外行為。以下是通常不應代理的常見標頭列表
  • host, accept
  • content-length, content-md5, content-type
  • x-forwarded-host, x-forwarded-port, x-forwarded-proto
  • cf-connecting-ip, cf-ray

useFetch

useFetch 可組合函式在底層使用 $fetch 在 setup 函式中進行 SSR 安全的網路呼叫。

app/app.vue
<script setup lang="ts">
const { data: count } = await useFetch('/api/count')
</script>

<template>
  <p>Page visits: {{ count }}</p>
</template>

這個可組合函式是 useAsyncData 可組合函式和 $fetch 工具的封裝。

閱讀更多資訊:Docs > 4 X > API > Composables > Use Fetch
Docs > 4 X > Examples > Features > Data Fetching 中閱讀和編輯即時示例。

useAsyncData

useAsyncData 可組合函式負責封裝非同步邏輯並在解析後返回結果。

useFetch(url) 幾乎等同於 useAsyncData(url, () => event.$fetch(url))
它是最常見用例的開發者體驗糖。(您可以從useRequestFetch 中找到更多關於 event.fetch 的資訊。)

在某些情況下,使用 useFetch 可組合函式是不合適的,例如當 CMS 或第三方提供自己的查詢層時。在這種情況下,您可以使用 useAsyncData 封裝您的呼叫,並仍然保留可組合函式提供的優點。

app/pages/users.vue
<script setup lang="ts">
const { data, error } = await useAsyncData('users', () => myGetFunction('users'))

// This is also possible:
const { data, error } = await useAsyncData(() => myGetFunction('users'))
</script>
useAsyncData 的第一個引數是一個唯一的鍵,用於快取第二個引數(查詢函式)的響應。如果直接傳遞查詢函式,可以忽略此鍵,鍵將自動生成。

由於自動生成的鍵僅考慮呼叫 useAsyncData 的檔案和行,因此建議始終建立自己的鍵,以避免意外行為,例如當您建立自己的自定義可組合函式封裝 useAsyncData 時。

設定鍵對於使用 useNuxtData 在元件之間共享相同資料或重新整理特定資料非常有用。
app/pages/users/[id].vue
<script setup lang="ts">
const { id } = useRoute().params

const { data, error } = await useAsyncData(`user:${id}`, () => {
  return myGetFunction('users', { id })
})
</script>

useAsyncData 可組合函式是封裝並等待多個 $fetch 請求完成,然後處理結果的好方法。

<script setup lang="ts">
const { data: discounts, status } = await useAsyncData('cart-discount', async () => {
  const [coupons, offers] = await Promise.all([
    $fetch('/cart/coupons'),
    $fetch('/cart/offers'),
  ])

  return { coupons, offers }
})
// discounts.value.coupons
// discounts.value.offers
</script>
useAsyncData 用於獲取和快取資料,而不是觸發副作用,例如呼叫 Pinia actions,因為這可能會導致意外行為,例如重複執行空值。如果您需要觸發副作用,請使用 callOnce 工具來執行此操作。
<script setup lang="ts">
const offersStore = useOffersStore()

// you can't do this
await useAsyncData(() => offersStore.getOffer(route.params.slug))
</script>
閱讀更多關於 useAsyncData 的資訊。

返回值

useFetchuseAsyncData 具有下面列出的相同返回值。

  • data:作為引數傳入的非同步函式的結果。
  • refresh/execute:一個可用於重新整理 handler 函式返回的資料的函式。
  • clear:一個可用於將 data 設定為 undefined(如果提供了 options.default(),則為該值),將 error 設定為 undefined,將 status 設定為 idle,並標記任何當前待處理的請求為已取消的函式。
  • error:如果資料獲取失敗,則為錯誤物件。
  • status:一個字串,指示資料請求的狀態("idle""pending""success""error")。
dataerrorstatus 是 Vue ref,可在 <script setup> 中透過 .value 訪問。

預設情況下,Nuxt 會等待 refresh 完成後才能再次執行。

如果你沒有在伺服器上獲取資料(例如,使用 server: false),那麼在水合完成之前資料將不會被獲取。這意味著即使你在客戶端等待 useFetchdata<script setup> 中仍將為 null。

選項

useAsyncDatauseFetch 返回相同的物件型別,並接受一組共同的選項作為它們的最後一個引數。它們可以幫助您控制可組合函式的行為,例如導航阻塞、快取或執行。

惰性

預設情況下,資料獲取可組合函式將等待其非同步函式解析後才導航到新頁面,透過使用 Vue 的 Suspense。此功能可以在客戶端導航時透過 lazy 選項忽略。在這種情況下,您將必須使用 status 值手動處理載入狀態。

app/app.vue
<script setup lang="ts">
const { status, data: posts } = useFetch('/api/posts', {
  lazy: true,
})
</script>

<template>
  <!-- you will need to handle a loading state -->
  <div v-if="status === 'pending'">
    Loading ...
  </div>
  <div v-else>
    <div v-for="post in posts">
      <!-- do something -->
    </div>
  </div>
</template>

您還可以使用 useLazyFetchuseLazyAsyncData 作為便捷方法來執行相同的操作。

<script setup lang="ts">
const { status, data: posts } = useLazyFetch('/api/posts')
</script>
閱讀更多關於 useLazyFetch 的資訊。
閱讀更多關於 useLazyAsyncData 的資訊。

僅客戶端獲取

預設情況下,資料獲取可組合函式將在客戶端和伺服器環境中執行其非同步函式。將 server 選項設定為 false 只在客戶端執行呼叫。在初始載入時,資料在水合完成之前不會被獲取,因此您必須處理掛起狀態,儘管在後續的客戶端導航中,資料將在載入頁面之前被等待。

lazy 選項結合使用,這對於在首次渲染時不需要的資料(例如,非 SEO 敏感資料)非常有用。

/* This call is performed before hydration */
const articles = await useFetch('/api/article')

/* This call will only be performed on the client */
const { status, data: comments } = useFetch('/api/comments', {
  lazy: true,
  server: false,
})

useFetch 可組合函式旨在在 setup 方法中呼叫,或直接在生命週期鉤子函式頂部呼叫,否則您應該使用 $fetch 方法

最小化有效負載大小

pick 選項透過僅選擇您希望從可組合函式返回的欄位來幫助您最小化儲存在 HTML 文件中的有效負載大小。

<script setup lang="ts">
/* only pick the fields used in your template */
const { data: mountain } = await useFetch('/api/mountains/everest', {
  pick: ['title', 'description'],
})
</script>

<template>
  <h1>{{ mountain.title }}</h1>
  <p>{{ mountain.description }}</p>
</template>

如果您需要更多控制或對映多個物件,可以使用 transform 函式來更改查詢結果。

const { data: mountains } = await useFetch('/api/mountains', {
  transform: (mountains) => {
    return mountains.map(mountain => ({ title: mountain.title, description: mountain.description }))
  },
})
picktransform 都不能阻止不需要的資料最初被獲取。但是它們會阻止不需要的資料被新增到從伺服器傳輸到客戶端的有效負載中。

快取和重新獲取

useFetchuseAsyncData 使用鍵來防止重複獲取相同的資料。

  • useFetch 使用提供的 URL 作為鍵。或者,可以在作為最後一個引數傳遞的 options 物件中提供 key 值。
  • useAsyncData 使用其第一個引數作為鍵(如果它是一個字串)。如果第一個引數是執行查詢的處理函式,則會為您生成一個對 useAsyncData 例項的檔名和行號唯一的鍵。
要透過鍵獲取快取資料,可以使用 useNuxtData

共享狀態和選項一致性

當多個元件使用相同的鍵與 useAsyncDatauseFetch 時,它們將共享相同的 dataerrorstatus 引用。這確保了元件之間的一致性,但要求某些選項保持一致。

以下選項必須在所有具有相同鍵的呼叫中保持一致

  • handler 函式
  • deep 選項
  • transform 函式
  • pick 陣列
  • getCachedData 函式
  • default
// ❌ This will trigger a development warning
const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { deep: false })
const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { deep: true })

以下選項可以安全地不同,而不會觸發警告

  • 伺服器
  • lazy
  • immediate
  • dedupe
  • watch
// ✅ This is allowed
const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { immediate: true })
const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { immediate: false })

如果您需要獨立的例項,請使用不同的鍵

// These are completely independent instances
const { data: users1 } = useAsyncData('users-1', () => $fetch('/api/users'))
const { data: users2 } = useAsyncData('users-2', () => $fetch('/api/users'))

響應式鍵

您可以使用計算屬性、普通 ref 或 getter 函式作為鍵,從而實現動態資料獲取,並在依賴項更改時自動更新

// Using a computed property as a key
const userId = ref('123')
const { data: user } = useAsyncData(
  computed(() => `user-${userId.value}`),
  () => fetchUser(userId.value),
)

// When userId changes, the data will be automatically refetched
// and the old data will be cleaned up if no other components use it
userId.value = '456'

重新整理和執行

如果您想手動獲取或重新整理資料,請使用可組合函式提供的 executerefresh 函式。

<script setup lang="ts">
const { data, error, execute, refresh } = await useFetch('/api/users')
</script>

<template>
  <div>
    <p>{{ data }}</p>
    <button @click="() => refresh()">
      Refresh data
    </button>
  </div>
</template>

execute 函式是 refresh 的別名,其工作方式完全相同,但在獲取不立即時更具語義。

要全域性重新獲取或使快取資料失效,請參閱 clearNuxtDatarefreshNuxtData

清除

如果您出於任何原因想要清除提供的資料,而無需知道要傳遞給 clearNuxtData 的特定鍵,您可以使用可組合函式提供的 clear 函式。

<script setup lang="ts">
const { data, clear } = await useFetch('/api/users')

const route = useRoute()
watch(() => route.path, (path) => {
  if (path === '/') {
    clear()
  }
})
</script>

監聽

為了在應用程式中其他響應式值更改時重新執行您的獲取函式,請使用 watch 選項。您可以將其用於一個或多個可觀察元素。

<script setup lang="ts">
const id = ref(1)

const { data, error, refresh } = await useFetch('/api/users', {
  /* Changing the id will trigger a refetch */
  watch: [id],
})
</script>

請注意,觀察響應式值不會改變已獲取的 URL。例如,這將繼續獲取使用者的相同初始 ID,因為 URL 是在函式被呼叫時構建的。

<script setup lang="ts">
const id = ref(1)

const { data, error, refresh } = await useFetch(`/api/users/${id.value}`, {
  watch: [id],
})
</script>

如果需要根據響應式值更改 URL,您可能希望改用計算 URL

當提供了響應式獲取選項時,它們將被自動監視並觸發重新獲取。在某些情況下,透過指定 watch: false 來選擇退出此行為會很有用。

const id = ref(1)

// Won't automatically refetch when id changes
const { data, execute } = await useFetch('/api/users', {
  query: { id }, // id is watched by default
  watch: false, // disables automatic watching of id
})

// doesn't trigger refetch
id.value = 2

計算 URL

有時您可能需要根據響應式值計算 URL,並在這些值每次更改時重新整理資料。您無需絞盡腦汁,而是可以將每個引數作為響應式值附加。Nuxt 將自動使用響應式值並在每次更改時重新獲取。

<script setup lang="ts">
const id = ref(null)

const { data, status } = useLazyFetch('/api/user', {
  query: {
    user_id: id,
  },
})
</script>

在更復雜的 URL 構建情況下,您可以將回調作為計算 getter返回 URL 字串。

每當依賴項更改時,都會使用新構建的 URL 獲取資料。將其與不立即結合使用,您可以等待響應式元素更改後再獲取。

<script setup lang="ts">
const id = ref(null)

const { data, status } = useLazyFetch(() => `/api/users/${id.value}`, {
  immediate: false,
})

const pending = computed(() => status.value === 'pending')
</script>

<template>
  <div>
    <!-- disable the input while fetching -->
    <input
      v-model="id"
      type="number"
      :disabled="pending"
    >

    <div v-if="status === 'idle'">
      Type an user ID
    </div>

    <div v-else-if="pending">
      Loading ...
    </div>

    <div v-else>
      {{ data }}
    </div>
  </div>
</template>

如果需要在其他響應式值更改時強制重新整理,您還可以觀察其他值

不立即

useFetch 可組合函式在被呼叫時將立即開始獲取資料。您可以透過設定 immediate: false 來阻止此行為,例如,等待使用者互動。

有了這個,您將需要 status 來處理獲取生命週期,以及 execute 來啟動資料獲取。

<script setup lang="ts">
const { data, error, execute, status } = await useLazyFetch('/api/comments', {
  immediate: false,
})
</script>

<template>
  <div v-if="status === 'idle'">
    <button @click="execute">
      Get data
    </button>
  </div>

  <div v-else-if="status === 'pending'">
    Loading comments...
  </div>

  <div v-else>
    {{ data }}
  </div>
</template>

為了更精細的控制,status 變數可以是

  • 當獲取尚未開始時為 idle
  • 當獲取已開始但尚未完成時為 pending
  • 當獲取失敗時為 error
  • 當獲取成功完成時為 success

傳遞頭和 Cookie

當我們在瀏覽器中呼叫 $fetch 時,使用者頭(如 cookie)將直接傳送到 API。

通常,在伺服器端渲染期間,出於安全考慮,$fetch 不會包含使用者的瀏覽器 cookie,也不會傳遞來自 fetch 響應的 cookie。

然而,當在伺服器上使用相對 URL 呼叫 useFetch 時,Nuxt 將使用 useRequestFetch 代理標頭和 cookie(不應轉發的標頭除外,例如 host)。

在 SSR 響應中從伺服器端 API 呼叫傳遞 Cookie

如果您想將 cookie 以另一個方向(從內部請求返回到客戶端)傳遞/代理,則需要自行處理。

app/composables/fetch.ts
import { appendResponseHeader } from 'h3'
import type { H3Event } from 'h3'

export const fetchWithCookie = async (event: H3Event, url: string) => {
  /* Get the response from the server endpoint */
  const res = await $fetch.raw(url)
  /* Get the cookies from the response */
  const cookies = res.headers.getSetCookie()
  /* Attach each cookie to our incoming Request */
  for (const cookie of cookies) {
    appendResponseHeader(event, 'set-cookie', cookie)
  }
  /* Return the data of the response */
  return res._data
}
<script setup lang="ts">
// This composable will automatically pass cookies to the client
const event = useRequestEvent()

const { data: result } = await useAsyncData(() => fetchWithCookie(event!, '/api/with-cookie'))

onMounted(() => console.log(document.cookie))
</script>

選項 API 支援

Nuxt 提供了在 Options API 中執行 asyncData 獲取的方法。為此,您必須將元件定義封裝在 defineNuxtComponent 中。

<script>
export default defineNuxtComponent({
  /* Use the fetchKey option to provide a unique key */
  fetchKey: 'hello',
  async asyncData () {
    return {
      hello: await $fetch('/api/hello'),
    }
  },
})
</script>
使用 <script setup><script setup lang="ts"> 是在 Nuxt 中宣告 Vue 元件的推薦方式。
文件 > 4 X > API > Utils > Define Nuxt Component 中閱讀更多資訊。

將資料從伺服器序列化到客戶端

當使用 useAsyncDatauseLazyAsyncData 將在伺服器上獲取的資料傳輸到客戶端(以及所有其他利用 useNuxtApp 的有效負載)時,有效負載會使用devalue進行序列化。這使我們不僅可以傳輸基本的 JSON,還可以序列化和復活/反序列化更高級別的資料,例如正則表示式、日期、Map 和 Set、refreactiveshallowRefshallowReactiveNuxtError 等。

還可以為 Nuxt 不支援的型別定義自己的序列化器/反序列化器。您可以在 useNuxtApp 文件中閱讀更多資訊。

請注意,這不適用於使用 $fetchuseFetch 從伺服器路由獲取的資料——有關更多資訊,請參閱下一節。

從 API 路由序列化資料

server 目錄獲取資料時,響應使用 JSON.stringify 序列化。然而,由於序列化僅限於 JavaScript 原始型別,Nuxt 會盡力轉換 $fetchuseFetch 的返回型別以匹配實際值。

瞭解更多關於 JSON.stringify 限制的資訊。

示例

server/api/foo.ts
export default defineEventHandler(() => {
  return new Date()
})
app/app.vue
<script setup lang="ts">
// Type of `data` is inferred as string even though we returned a Date object
const { data } = await useFetch('/api/foo')
</script>

自定義序列化函式

要自定義序列化行為,您可以在返回的物件上定義 toJSON 函式。如果您定義了 toJSON 方法,Nuxt 將遵循該函式的返回型別,並且不會嘗試轉換型別。

server/api/bar.ts
export default defineEventHandler(() => {
  const data = {
    createdAt: new Date(),

    toJSON () {
      return {
        createdAt: {
          year: this.createdAt.getFullYear(),
          month: this.createdAt.getMonth(),
          day: this.createdAt.getDate(),
        },
      }
    },
  }
  return data
})
app/app.vue
<script setup lang="ts">
// Type of `data` is inferred as
// {
//   createdAt: {
//     year: number
//     month: number
//     day: number
//   }
// }
const { data } = await useFetch('/api/bar')
</script>

使用替代序列化器

Nuxt 目前不支援 JSON.stringify 的替代序列化器。但是,您可以將有效負載作為普通字串返回,並使用 toJSON 方法來保持型別安全。

在下面的示例中,我們使用superjson作為我們的序列化器。

server/api/superjson.ts
import superjson from 'superjson'

export default defineEventHandler(() => {
  const data = {
    createdAt: new Date(),

    // Workaround the type conversion
    toJSON () {
      return this
    },
  }

  // Serialize the output to string, using superjson
  return superjson.stringify(data) as unknown as typeof data
})
app/app.vue
<script setup lang="ts">
import superjson from 'superjson'

// `date` is inferred as { createdAt: Date } and you can safely use the Date object methods
const { data } = await useFetch('/api/superjson', {
  transform: (value) => {
    return superjson.parse(value as unknown as string)
  },
})
</script>

秘訣

透過 POST 請求消費 SSE(伺服器傳送事件)

如果您透過 GET 請求消費 SSE,您可以使用EventSource或 VueUse 可組合函式useEventSource.

當透過 POST 請求消費 SSE 時,您需要手動處理連線。以下是您可以如何操作:

// Make a POST request to the SSE endpoint
const response = await $fetch<ReadableStream>('/chats/ask-ai', {
  method: 'POST',
  body: {
    query: 'Hello AI, how are you?',
  },
  responseType: 'stream',
})

// Create a new ReadableStream from the response with TextDecoderStream to get the data as text
const reader = response.pipeThrough(new TextDecoderStream()).getReader()

// Read the chunk of data as we get it
while (true) {
  const { value, done } = await reader.read()

  if (done) { break }

  console.log('Received:', value)
}

並行請求

當請求之間沒有依賴關係時,您可以使用 Promise.all() 並行發起請求以提高效能。

const { data } = await useAsyncData(() => {
  return Promise.all([
    $fetch('/api/comments/'),
    $fetch('/api/author/12'),
  ])
})

const comments = computed(() => data.value?.[0])
const author = computed(() => data.value?.[1])