データフェッチ
Nuxt には、ブラウザまたはサーバー環境でデータフェッチを実行するための 2 つのコンポーザブルと組み込みライブラリが付属しています。useFetch、useAsyncData、および $fetch です。
要するに
$fetchは、ネットワークリクエストを行う最も簡単な方法です。useFetchは、$fetchのラッパーで、ユニバーサルレンダリングでデータを一度だけフェッチします。useAsyncDataはuseFetchに似ていますが、よりきめ細かい制御を提供します。
useFetch と useAsyncData は、最後のセクションで詳しく説明する共通のオプションとパターンを共有しています。
useFetch と useAsyncData の必要性
Nuxt は、サーバーとクライアントの両方の環境で等形 (またはユニバーサル) コードを実行できるフレームワークです。Vue コンポーネントのセットアップ関数でデータフェッチに $fetch 関数 を使用すると、データが 2 回フェッチされる可能性があります。1 回はサーバーで (HTML をレンダリングするため)、もう 1 回はクライアントで (HTML がハイドレートされたとき) です。これにより、ハイドレーションの問題が発生したり、インタラクションまでの時間が増加したり、予測できない動作を引き起こしたりする可能性があります。
useFetch および useAsyncData コンポーザブルは、API 呼び出しがサーバーで行われた場合、データがペイロードでクライアントに転送されるようにすることで、この問題を解決します。
ペイロードは、useNuxtApp().payload を介してアクセスできる JavaScript オブジェクトです。これは、ハイドレーション中にブラウザでコードが実行されるときに、同じデータの再フェッチを回避するためにクライアントで使用されます。
<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 エイリアスとして自動インポートされます。
<script setup lang="ts">
async function addTodo () {
const todo = await $fetch('/api/todos', {
method: 'POST',
body: {
// My todo data
},
})
}
</script>
$fetch のみを使用しても、ネットワーク呼び出しの重複排除とナビゲーションの防止は提供されないことに注意してください。クライアント側のインタラクション (イベントベース) または初期コンポーネントデータをフェッチするときに
useAsyncData と組み合わせて $fetch を使用することをお勧めします。クライアントヘッダーを API に渡す
サーバーで useFetch を呼び出すと、Nuxt は useRequestFetch を使用して、クライアントヘッダーとクッキーをプロキシします (host など、転送すべきではないヘッダーを除く)。
<script setup lang="ts">
const { data } = await useFetch('/api/echo')
</script>
// /api/echo.ts
export default defineEventHandler(event => parseCookies(event))
あるいは、以下の例は、useRequestHeaders を使用して、サーバー側のリクエスト (クライアントから発信) から API にクッキーにアクセスして送信する方法を示しています。等形 $fetch 呼び出しを使用することで、API エンドポイントがユーザーのブラウザによって最初に送信されたのと同じ cookie ヘッダーにアクセスできるようにします。これは、useFetch を使用していない場合にのみ必要です。
<script setup lang="ts">
const headers = useRequestHeaders(['cookie'])
async function getCurrentUser () {
return await $fetch('/api/me', { headers })
}
</script>
useRequestFetch を使用して、ヘッダーを呼び出しに自動的にプロキシすることもできます。host、acceptcontent-length、content-md5、content-typex-forwarded-host、x-forwarded-port、x-forwarded-protocf-connecting-ip、cf-ray
useFetch
useFetch コンポーザブルは、セットアップ関数で SSR セーフなネットワーク呼び出しを行うために、内部で $fetch を使用します。
<script setup lang="ts">
const { data: count } = await useFetch('/api/count')
</script>
<template>
<p>Page visits: {{ count }}</p>
</template>
このコンポーザブルは、useAsyncData コンポーザブルと $fetch ユーティリティのラッパーです。
useAsyncData
useAsyncData コンポーザブルは、非同期ロジックをラップし、解決された結果を返す役割を担っています。
useFetch(url) は、ほぼ useAsyncData(url, () => event.$fetch(url)) と同等です。これは、最も一般的なユースケース向けのデベロッパーエクスペリエンスシュガーです。(
event.fetch の詳細については、useRequestFetch を参照してください。)CMS やサードパーティが独自のクエリレイヤーを提供する場合など、useFetch コンポーザブルが適切でない場合があります。この場合、useAsyncData を使用して呼び出しをラップし、コンポーザブルが提供する利点を維持できます。
<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 の最初の引数は、2 番目の引数であるクエリ関数の応答をキャッシュするために使用される一意のキーです。このキーは、クエリ関数を直接渡すことで無視できます。キーは自動生成されます。自動生成されるキーは、
useAsyncData が呼び出されるファイルと行のみを考慮するため、useAsyncData をラップする独自のカスタムコンポーザブルを作成する場合など、予期しない動作を避けるために、常に独自のキーを作成することをお勧めします。キーを設定することは、
useNuxtData を使用してコンポーネント間で同じデータを共有したり、特定のデータを更新したりするのに役立ちます。<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 アクションの呼び出しなどの副作用を引き起こすものではありません。これは、null 値による繰り返しの実行などの意図しない動作を引き起こす可能性があるためです。副作用をトリガーする必要がある場合は、callOnce ユーティリティを使用してください。<script setup lang="ts">
const offersStore = useOffersStore()
// you can't do this
await useAsyncData(() => offersStore.getOffer(route.params.slug))
</script>
戻り値
useFetch と useAsyncData は、以下の同じ戻り値を持ちます。
data: 渡された非同期関数の結果。refresh/execute:handler関数によって返されるデータを更新するために使用できる関数。clear:dataをundefined(または、提供されている場合はoptions.default()の値) に設定し、errorをundefinedに設定し、statusをidleに設定し、現在保留中のリクエストをすべてキャンセル済みとしてマークするために使用できる関数。error: データフェッチが失敗した場合のエラーオブジェクト。status: データリクエストのステータスを示す文字列 ("idle"、"pending"、"success"、"error")。
data、error、status は、<script setup> で .value を使用してアクセスできる Vue refs です。デフォルトでは、Nuxt は refresh が完了するまで待機し、再度実行できるようにします。
server: false を使用している場合)、ハイドレーションが完了するまでデータはフェッチされません。これは、クライアント側で useFetch を待機しても、<script setup> 内の data は null のままになることを意味します。オプション
useAsyncData と useFetch は同じオブジェクトタイプを返し、最後の引数として共通のオプションセットを受け入れます。これらは、ナビゲーションのブロック、キャッシュ、実行など、コンポーザブルの動作を制御するのに役立ちます。
遅延
デフォルトでは、データフェッチコンポーザブルは Vue の Suspense を使用して、非同期関数の解決を待ってから新しいページに移動します。この機能は、lazy オプションを使用してクライアント側のナビゲーションで無視できます。その場合、status 値を使用して手動で読み込み状態を処理する必要があります。
<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>
代わりに、useLazyFetch と useLazyAsyncData を便利なメソッドとして使用して、同じことを実行できます。
<script setup lang="ts">
const { status, data: posts } = useLazyFetch('/api/posts')
</script>
クライアントのみのフェッチ
デフォルトでは、データフェッチコンポーザブルは、クライアント環境とサーバー環境の両方で非同期関数を実行します。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 コンポーザブルは、セットアップメソッドで呼び出すか、ライフサイクルフックの関数のトップレベルで直接呼び出すことを目的としています。それ以外の場合は、$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 }))
},
})
pick と transform の両方とも、不要なデータが最初にフェッチされるのを防ぐことはありません。しかし、サーバーからクライアントに転送されるペイロードに不要なデータが追加されるのを防ぎます。キャッシュと再フェッチ
キー
useFetch と useAsyncData は、同じデータの再フェッチを防ぐためにキーを使用します。
useFetchは、提供された URL をキーとして使用します。あるいは、最後の引数として渡されるoptionsオブジェクトでkey値を提供することもできます。useAsyncDataは、最初の引数が文字列の場合、それをキーとして使用します。最初の引数がクエリを実行するハンドラー関数である場合、useAsyncDataのインスタンスのファイル名と行番号に固有のキーが自動生成されます。
useNuxtData を使用できます。共有状態とオプションの一貫性
複数のコンポーネントが useAsyncData または useFetch と同じキーを使用する場合、同じ data、error、および status ref を共有します。これにより、コンポーネント間の一貫性が保証されますが、一部のオプションは一貫している必要があります。
以下のオプションは、同じキーを持つすべての呼び出しで一貫している必要があります。
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 })
以下のオプションは、警告をトリガーすることなく安全に異なる場合があります。
サーバーlazyimmediatededupewatch
// ✅ 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、プレーンな ref、またはゲッター関数をキーとして使用できます。これにより、依存関係が変更されると自動的に更新される動的なデータフェッチが可能になります。
// 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'
リフレッシュと実行
データを手動でフェッチまたは更新したい場合は、コンポーザブルが提供する execute または refresh 関数を使用します。
<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 のエイリアスで、まったく同じように機能しますが、フェッチが即時でない場合に、より意味的です。
clearNuxtData と refreshNuxtData を参照してください。クリア
どのような理由であれ、提供されたデータをクリアしたい場合で、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 オプションを使用します。1 つまたは複数の監視可能な要素に対して使用できます。
<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 は変更されません。たとえば、これは関数が呼び出された時点で URL が構築されるため、同じ初期ユーザー ID をフェッチし続けます。
<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 構築の場合、計算ゲッターとしてコールバックを使用できます。これは 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
ヘッダーとクッキーの受け渡し
ブラウザで $fetch を呼び出すと、cookie などのユーザーヘッダーが直接 API に送信されます。
通常、サーバーサイドレンダリング中、セキュリティ上の考慮事項により、$fetch はユーザーのブラウザクッキーを含まず、フェッチ応答からのクッキーも渡しません。
ただし、サーバーで相対 URL を指定して useFetch を呼び出すと、Nuxt は useRequestFetch を使用して、ヘッダーとクッキーをプロキシします (host など、転送すべきでないヘッダーを除く)。
SSR 応答でサーバー側 API 呼び出しからクッキーを渡す
もう一方の方向、つまり内部リクエストからクライアントにクッキーを渡す/プロキシしたい場合は、自分で処理する必要があります。
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 コンポーネントを宣言する推奨される方法です。サーバーからクライアントへのデータのシリアル化
useAsyncData および useLazyAsyncData を使用して、サーバーでフェッチされたデータをクライアントに転送する場合 (および Nuxt ペイロードを利用するその他すべて)、ペイロードはdevalueでシリアル化されます。これにより、基本的な JSON だけでなく、正規表現、日付、Map、Set、ref、reactive、shallowRef、shallowReactive、NuxtError など、より高度な種類のデータをシリアル化して復元/逆シリアル化できます。
Nuxt がサポートしていない型に対して独自のシリアライザー/デシリアライザーを定義することも可能です。useNuxtApp のドキュメントで詳細を確認できます。
$fetch または useFetch でフェッチされたサーバー経路からのデータには適用されないことに注意してください。詳細については、次のセクションを参照してください。API ルートからのデータのシリアル化
server ディレクトリからデータをフェッチする場合、応答は JSON.stringify を使用してシリアル化されます。ただし、シリアル化は JavaScript のプリミティブ型のみに制限されているため、Nuxt は $fetch および useFetch の戻り値の型を実際の値と一致させるために最善を尽くします。
例
export default defineEventHandler(() => {
return new Date()
})
<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 は関数の戻り値の型を尊重し、型の変換を試みません。
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
})
<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をシリアライザーとして使用しています。
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
})
<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 (Server-Sent Events) の利用
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])