データ取得
Nuxtはアプリケーション内でのデータフェッチを処理するためのcomposablesを提供しています。
Nuxtはブラウザまたはサーバ環境でデータフェッチを行うための2つのcomposablesと組み込みライブラリを備えています: useFetch
、useAsyncData
そして $fetch
。
簡単に説明すると:
$fetch
はネットワークリクエストを行う最もシンプルな方法です。useFetch
は$fetch
をラップして、ユニバーサルレンダリングで一度だけデータをフェッチします。useAsyncData
はuseFetch
と似ていますが、より細かい制御を提供します。
useFetch
と useAsyncData
は共通のオプションとパターンを持っており、最後のセクションで詳しく説明します。
useFetch
と useAsyncData
が必要な理由
Nuxtはサーバーとクライアントの両方の環境でアイソモーフィック(またはユニバーサル)コードを実行するフレームワークです。Vueコンポーネントのセットアップ関数で $fetch
関数 を使用してデータフェッチを行うと、サーバーで一度(HTMLをレンダリングするため)とクライアントで再度(HTMLがハイドレートされるとき)の2回、データがフェッチされる可能性があります。これはハイドレーションの問題、インタラクティブへの到達時間の増加、予測不可能な挙動を引き起こす可能性があります。
useFetch
と useAsyncData
のcomposablesは、サーバーでAPI呼び出しが行われた場合、データがペイロードを介してクライアントに転送されることを保証することでこの問題を解決します。
ペイロードは、useNuxtApp().payload
を通じてアクセス可能なJavaScriptオブジェクトであり、コードがブラウザで実行される際ハイドレーション中に同じデータを再フェッチするのを避けるためにクライアントで使用されます。
Nuxt DevTools を使用して、Payloadタブでこのデータを確認してください。
<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 == null">
データがありません
</div>
<div v-else>
<form @submit="handleFormSubmit">
<!-- フォーム入力タグ -->
</form>
</div>
</template>
上記の例では、useFetch
はリクエストがサーバーで行われ、適切にブラウザに転送されることを保証します。$fetch
にはそのようなメカニズムはありませんが、リクエストがブラウザからのみ行われる場合に使用するのに適しています。
Suspense
Nuxtは内部的にVueの <Suspense>
コンポーネントを使用して、すべての非同期データがビューに利用可能になる前にナビゲーションを防止します。データフェッチングのcomposablesを活用して、この機能を使用し、呼び出し毎に最適なオプションを選択することができます。
<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
を使用してクライアントのヘッダーとクッキーをプロキシします(ホストなどの転送されるべきではないヘッダーを除く)。
const { data } = await useFetch('/api/echo');
オプション
useAsyncData
および useFetch
は同じオブジェクト型を返し、最後の引数として共通のオプションセットを受け入れます。これにより、ナビゲーションのブロック、キャッシュ、実行などのcomposablesの動作を制御できます。
Lazy
デフォルトでは、データ取得composablesはVueのSuspenseを使用して新しいページにナビゲートする前に非同期関数の解決を待ちます。この機能はクライアントサイドのナビゲーションでは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
クライアントのみのフェッチング
デフォルトでは、データ取得composablesはクライアントとサーバーの両環境で非同期関数を実行します。クライアントサイドのみで実行を行う場合は、server
オプションをfalse
に設定します。最初のロード時にはデータがフェッチされず、クライアントサイドナビゲーションの際にはページのロード前にデータが待たれます。
lazy
オプションと組み合わせることで、最初のレンダリングには必要ないデータ(例えば、SEOに敏感でないデータ)に役立ちます。
/* この呼び出しはハイドレーション前に行われます */
const articles = await useFetch('/api/article')
/* この呼び出しはクライアント上でのみ実行されます */
const { status, data: comments } = useFetch('/api/comments', {
lazy: true,
server: false
})
useFetch
composableはsetupメソッドで呼び出されるか、ライフサイクルフックの関数のトップレベルで直接呼び出されるべきです。それ以外では$fetch
メソッドを使用するべきです。
ペイロードサイズの最小化
pick
オプションを使用すると、HTMLドキュメントに格納されるペイロードサイズを最小化して、composablesから返されるフィールドのみを選択できます。
<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
refs を共有します。これによりコンポーネント間での一貫性が保証されますが、オプションの一部は一貫したものでなければなりません。
以下のオプションは一貫している必要があります:
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'))
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'
// `data` の推論は { createdAt: Date } となり、Date オブジェクトのメソッドを安全に使用できる
const { data } = await useFetch('/api/superjson', {
transform: (value) => {
return superjson.parse(value as unknown as string)
},
})
レシピ
POST リクエストを通じて SSE (Server-Sent Events) を利用する
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/3.x/getting-started/data-fetching