データフェッチング
Nuxtはアプリケーション内でデータフェッチングを処理するためのコンポーザブルを提供します。
Nuxtは、ブラウザまたはサーバー環境でデータフェッチングを行うための2つのコンポーザブルと組み込みライブラリを提供しています:useFetch、useAsyncData、および$fetchです。
要約すると:
$fetchはネットワークリクエストを行う最も簡単な方法です。useFetchは、ユニバーサルレンダリングでデータを一度だけフェッチする$fetchのラッパーです。useAsyncDataはuseFetchに似ていますが、より細かい制御を提供します。
useFetchとuseAsyncDataは共通のオプションとパターンを共有しており、これらについては最後のセクションで詳しく説明します。
useFetchとuseAsyncDataの必要性
Nuxtは、サーバーとクライアントの両方の環境でアイソモーフィック(またはユニバーサル)コードを実行できるフレームワークです。Vueコンポーネントのセットアップ関数で$fetch関数を使用してデータフェッチを行うと、データがサーバーで一度(HTMLをレンダリングするため)とクライアントで再度(HTMLがハイドレートされるとき)フェッチされる可能性があります。これにより、ハイドレーションの問題が発生し、インタラクティビティまでの時間が増加し、予測不可能な動作を引き起こす可能性があります。
useFetchとuseAsyncDataのコンポーザブルは、APIコールがサーバーで行われた場合、データがペイロードでクライアントに転送されることを保証することでこの問題を解決します。
ペイロードは、useNuxtApp().payloadを通じてアクセス可能なJavaScriptオブジェクトです。これは、ハイドレーション中にブラウザでコードが実行されるときに同じデータを再フェッチすることを避けるためにクライアントで使用されます。
このデータをPayloadタブで検査するためにNuxt DevToolsを使用してください。
<script setup lang="ts">
const { data } = await useFetch('/api/data')
async function handleFormSubmit() {
const res = await $fetch('/api/submit', {
method: 'POST',
body: {
// フォームデータ
}
})
}
</script>
<template>
<div v-if="data == undefined">
No data
</div>
<div v-else>
<form @submit="handleFormSubmit">
<!-- フォーム入力タグ -->
</form>
</div>
</template>
上記の例では、useFetchはリクエストがサーバーで発生し、ブラウザに適切に転送されることを保証します。$fetchにはそのようなメカニズムはなく、リクエストがブラウザからのみ行われる場合に使用するのがより良い選択です。
サスペンス
NuxtはVueの<Suspense>コンポーネントを内部で使用して、すべての非同期データがビューに利用可能になる前にナビゲーションを防ぎます。データフェッチングコンポーザブルは、この機能を活用し、コールごとに最適なものを使用するのに役立ちます。
ページナビゲーション間にプログレスバーを追加するために<NuxtLoadingIndicator>を追加できます。
$fetch
Nuxtにはofetchライブラリが含まれており、アプリケーション全体で$fetchエイリアスとして自動インポートされます。
async function addTodo() {
const todo = await $fetch('/api/todos', {
method: 'POST',
body: {
// Todoデータ
}
})
}$fetchのみを使用すると、ネットワークコールの重複排除とナビゲーション防止が提供されないことに注意してください。 :br
クライアントサイドのインタラクション(イベントベース)や初期コンポーネントデータをフェッチする際にuseAsyncDataと組み合わせて使用することをお勧めします。
クライアントヘッダーをAPIに渡す
サーバーでuseFetchを呼び出すと、NuxtはuseRequestFetchを使用してクライアントヘッダーとクッキーをプロキシします(hostのように転送されるべきでないヘッダーを除く)。
const { data } = await useFetch('/api/echo');
// /api/echo.ts
export default defineEventHandler(event => parseCookies(event))
また、以下の例では、useRequestHeadersを使用して、クライアントから発信されるサーバーサイドリクエストからクッキーをアクセスして送信する方法を示しています。アイソモーフィックな$fetchコールを使用することで、APIエンドポイントがユーザーのブラウザによって元々送信された同じcookieヘッダーにアクセスできることを保証します。これはuseFetchを使用していない場合にのみ必要です。
const headers = useRequestHeaders(['cookie'])
async function getCurrentUser() {
return await $fetch('/api/me', { headers })
}
useRequestFetchを使用して、ヘッダーを自動的にコールにプロキシすることもできます。
外部APIにヘッダーをプロキシする前に非常に注意し、必要なヘッダーのみを含めてください。すべてのヘッダーが安全にバイパスできるわけではなく、望ましくない動作を引き起こす可能性があります。プロキシしてはいけない一般的なヘッダーのリストは以下の通りです:
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>ページ訪問数: {{ count }}</p>
</template>
このコンポーザブルは、useAsyncDataコンポーザブルと$fetchユーティリティのラッパーです。
useAsyncData
useAsyncDataコンポーザブルは、非同期ロジックをラップし、解決された結果を返す役割を担います。
useFetch(url)はuseAsyncData(url, () => event.$fetch(url))とほぼ同等です。 :br
これは最も一般的なユースケースのための開発者体験の糖衣です。(event.fetchについての詳細はuseRequestFetchで確認できます。)
useFetchコンポーザブルが適切でない場合がいくつかあります。例えば、CMSやサードパーティが独自のクエリレイヤーを提供する場合です。この場合、useAsyncDataを使用してコールをラップし、コンポーザブルが提供する利点を維持することができます。
const { data, error } = await useAsyncData('users', () => myGetFunction('users'))
// これも可能です:
const { data, error } = await useAsyncData(() => myGetFunction('users'))useAsyncDataの最初の引数は、クエリ関数の応答をキャッシュするために使用される一意のキーです。このキーはクエリ関数を直接渡すことで無視され、自動生成されます。
:br :br
自動生成されたキーはuseAsyncDataが呼び出されたファイルと行のみを考慮するため、useAsyncDataをラップするカスタムコンポーザブルを作成する場合など、望ましくない動作を避けるために常に独自のキーを作成することをお勧めします。
:br :br
キーを設定することで、useNuxtDataを使用してコンポーネント間で同じデータを共有したり、特定のデータをリフレッシュするのに役立ちます。
const { id } = useRoute().params
const { data, error } = await useAsyncData(`user:${id}`, () => {
return myGetFunction('users', { id })
})useAsyncDataコンポーザブルは、複数の$fetchリクエストが完了するのを待ち、その結果を処理するための優れた方法です。
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
useAsyncDataはデータのフェッチとキャッシュのためのものであり、Piniaアクションの呼び出しのような副作用を引き起こすためのものではありません。これは、nullish値での繰り返し実行などの意図しない動作を引き起こす可能性があります。副作用を引き起こす必要がある場合は、callOnceユーティリティを使用してください。
const offersStore = useOffersStore()
// これはできません
await useAsyncData(() => offersStore.getOffer(route.params.slug))戻り値
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のrefです。
デフォルトでは、Nuxtはrefreshが完了するまで待機し、その後再度実行されます。
サーバーでデータをフェッチしていない場合(例えば、server: falseを使用している場合)、データはハイドレーションが完了するまでフェッチされません。これは、クライアントサイドでuseFetchを待機しても、<script setup>内でdataがnullのままであることを意味します。
オプション
useAsyncDataとuseFetchは、同じオブジェクトタイプを返し、共通のオプションセットを最後の引数として受け入れます。これらは、ナビゲーションのブロック、キャッシング、または実行など、コンポーザブルの動作を制御するのに役立ちます。
レイジー
デフォルトでは、データフェッチングコンポーザブルは、Vueのサスペンスを使用して新しいページにナビゲートする前に非同期関数の解決を待ちます。この機能は、lazyオプションを使用してクライアントサイドのナビゲーションで無視できます。その場合、status値を使用して手動でロード状態を処理する必要があります。
<script setup lang="ts">
const { status, data: posts } = useFetch('/api/posts', {
lazy: true
})
</script>
<template>
<!-- ロード状態を処理する必要があります -->
<div v-if="status === 'pending'">
Loading ...
</div>
<div v-else>
<div v-for="post in posts">
<!-- 何かをする -->
</div>
</div>
</template>
代わりに、useLazyFetchとuseLazyAsyncDataを使用して同じことを行う便利な方法として使用できます。
const { status, data: posts } = useLazyFetch('/api/posts')
こちらも参照 api > composables > use-lazy-fetch
こちらも参照 api > composables > use-lazy-async-data
クライアントのみのフェッチ
デフォルトでは、データフェッチングコンポーザブルはクライアントとサーバーの両方の環境で非同期関数を実行します。serverオプションをfalseに設定して、クライアントサイドでのみコールを実行します。初回ロード時には、ハイドレーションが完了するまでデータはフェッチされませんが、その後のクライアントサイドナビゲーションでは、ページを読み込む前にデータが待機されます。
lazyオプションと組み合わせることで、初回レンダリング時に必要ないデータ(例えば、SEOに敏感でないデータ)に役立ちます。
/* このコールはハイドレーション前に実行されます */
const articles = await useFetch('/api/article')
/* このコールはクライアントでのみ実行されます */
const { status, data: comments } = useFetch('/api/comments', {
lazy: true,
server: false
})
useFetchコンポーザブルは、セットアップメソッドで呼び出されるか、ライフサイクルフック内の関数のトップレベルで直接呼び出されることを意図しています。それ以外の場合は、$fetchメソッドを使用する必要があります。
ペイロードサイズの最小化
pickオプションは、コンポーザブルから返されるフィールドを選択することで、HTMLドキュメントに保存されるペイロードサイズを最小化するのに役立ちます。
<script setup lang="ts">
/* テンプレートで使用するフィールドのみを選択 */
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値
// ❌ これは開発警告を引き起こします
const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { deep: false })
const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { deep: true })
以下のオプションは、警告を引き起こすことなく安全に異なることができます:
serverlazyimmediatededupewatch
// ✅ これは許可されています
const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { immediate: true })
const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { immediate: false })
独立したインスタンスが必要な場合は、異なるキーを使用してください:
// これらは完全に独立したインスタンスです
const { data: users1 } = useAsyncData('users-1', () => $fetch('/api/users'))
const { data: users2 } = useAsyncData('users-2', () => $fetch('/api/users'))
リアクティブキー
計算されたref、プレーンref、またはゲッター関数をキーとして使用することで、依存関係が変化したときに自動的に更新される動的データフェッチを可能にします:
// 計算プロパティをキーとして使用
const userId = ref('123')
const { data: user } = useAsyncData(
computed(() => `user-${userId.value}`),
() => fetchUser(userId.value)
)
// userIdが変わると、データは自動的に再フェッチされ、
// 他のコンポーネントが使用していない場合は古いデータがクリーンアップされます
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()">データをリフレッシュ</button>
</div>
</template>
execute関数はrefreshのエイリアスであり、まったく同じ方法で機能しますが、即時でない場合にフェッチを行う場合にはより意味があります。
キャッシュされたデータをグローバルに再フェッチまたは無効化するには、clearNuxtDataとrefreshNuxtDataを参照してください。
クリア
何らかの理由で提供されたデータをクリアしたい場合、特定のキーをclearNuxtDataに渡す必要なく、コンポーザブルによって提供されるclear関数を使用できます。
const { data, clear } = await useFetch('/api/users')
const route = useRoute()
watch(() => route.path, (path) => {
if (path === '/') clear()
})
ウォッチ
アプリケーション内の他のリアクティブな値が変化するたびにフェッチ関数を再実行するには、watchオプションを使用します。これは、1つまたは複数のウォッチ可能な要素に使用できます。
const id = ref(1)
const { data, error, refresh } = await useFetch('/api/users', {
/* idが変わると再フェッチがトリガーされます */
watch: [id]
})
リアクティブな値をウォッチしても、フェッチされるURLは変更されないことに注意してください。例えば、これはユーザーの初期IDをフェッチし続けます。なぜなら、関数が呼び出された時点でURLが構築されるからです。
const id = ref(1)
const { data, error, refresh } = await useFetch(`/api/users/${id.value}`, {
watch: [id]
})
リアクティブな値に基づいてURLを変更する必要がある場合は、代わりに計算されたURLを使用することを検討してください。
計算されたURL
時には、リアクティブな値からURLを計算し、これらが変化するたびにデータをリフレッシュする必要があるかもしれません。複雑な方法を使わずに、各パラメータをリアクティブな値として添付できます。Nuxtはリアクティブな値を自動的に使用し、それが変化するたびに再フェッチします。
const id = ref(null)
const { data, status } = useLazyFetch('/api/user', {
query: {
user_id: id
}
})
より複雑な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>
{/* フェッチ中は入力を無効にする */}
<input v-model="id" type="number" :disabled="pending"/>
<div v-if="status === 'idle'">
ユーザーIDを入力してください
</div>
<div v-else-if="pending">
ロード中 ...
</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">データを取得</button>
</div>
<div v-else-if="status === 'pending'">
コメントをロード中...
</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) => {
/* サーバーエンドポイントからの応答を取得 */
const res = await $fetch.raw(url)
/* 応答からクッキーを取得 */
const cookies = res.headers.getSetCookie()
/* 各クッキーを受信リクエストに添付 */
for (const cookie of cookies) {
appendResponseHeader(event, 'set-cookie', cookie)
}
/* 応答のデータを返す */
return res._data
}
// このコンポーザブルはクッキーをクライアントに自動的に渡します
const event = useRequestEvent()
const { data: result } = await useAsyncData(() => fetchWithCookie(event!, '/api/with-cookie'))
onMounted(() => console.log(document.cookie))
オプションAPIサポート
Nuxtは、オプションAPI内でasyncDataフェッチを実行する方法を提供します。これを機能させるには、コンポーネント定義をdefineNuxtComponent内でラップする必要があります。
export default defineNuxtComponent({
/* 一意のキーを提供するためにfetchKeyオプションを使用 */
fetchKey: 'hello',
async asyncData () {
return {
hello: await $fetch('/api/hello')
}
}
})
<script setup>または<script setup lang="ts">を使用してVueコンポーネントを宣言することがNuxtで推奨される方法です。
サーバーからクライアントへのデータのシリアライズ
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()
})
// `data`の型は文字列として推論されますが、Dateオブジェクトを返しました
const { data } = await useFetch('/api/foo')カスタムシリアライザ関数
シリアライズの動作をカスタマイズするには、返されるオブジェクトに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
})
// `data`の型は次のように推論されます
// {
// createdAt: {
// year: number
// month: number
// day: number
// }
// }
const { data } = await useFetch('/api/bar')代替シリアライザの使用
Nuxtは現在、JSON.stringifyの代替シリアライザをサポートしていません。ただし、ペイロードを通常の文字列として返し、型の安全性を維持するためにtoJSONメソッドを利用できます。
以下の例では、superjsonをシリアライザとして使用しています。
import superjson from 'superjson'
export default defineEventHandler(() => {
const data = {
createdAt: new Date(),
// 型変換を回避する
toJSON() {
return this
}
}
// superjsonを使用して出力を文字列にシリアライズ
return superjson.stringify(data) as unknown as typeof data
})
import superjson from 'superjson'
// `date`は{ createdAt: Date }として推論され、Dateオブジェクトのメソッドを安全に使用できます
const { data } = await useFetch('/api/superjson', {
transform: (value) => {
return superjson.parse(value as unknown as string)
},
})レシピ
POSTリクエストを介したSSE(サーバー送信イベント)の消費
GETリクエストを介してSSEを消費する場合は、EventSourceまたはVueUseコンポーザブルuseEventSourceを使用できます。
POSTリクエストを介してSSEを消費する場合、接続を手動で処理する必要があります。以下はその方法です:
// SSEエンドポイントにPOSTリクエストを行う
const response = await $fetch<ReadableStream>('/chats/ask-ai', {
method: 'POST',
body: {
query: "Hello AI, how are you?",
},
responseType: 'stream',
})
// TextDecoderStreamを使用してデータをテキストとして取得するために応答から新しいReadableStreamを作成
const reader = response.pipeThrough(new TextDecoderStream()).getReader()
// データのチャンクを取得するたびに読み取る
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]);
※このページは Nuxt.js 公式ドキュメントの翻訳ページです。
公式ドキュメントの該当ページはこちら:
https://nuxt.com/docs/4.x/getting-started/data-fetching