データフェッチング
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
,accept
content-length
,content-md5
,content-type
x-forwarded-host
,x-forwarded-port
,x-forwarded-proto
cf-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 })
以下のオプションは、警告を引き起こすことなく安全に異なることができます:
server
lazy
immediate
dedupe
watch
// ✅ これは許可されています
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