データ取得
Nuxtには、ブラウザまたはサーバー環境でデータ取得を実行するための2つのコンポーザブルと組み込みライブラリが用意されています。useFetch
、useAsyncData
、および$fetch
です。
要約すると
$fetch
は、ネットワークリクエストを行う最も簡単な方法です。useFetch
は$fetch
のラッパーであり、ユニバーサルレンダリングではデータを一度だけ取得します。useAsyncData
はuseFetch
に似ていますが、より細かい制御が可能です。
useFetch
とuseAsyncData
は、共通のオプションとパターンを共有しており、最後のセクションで詳しく説明します。
useFetch
とuseAsyncData
が必要な理由
Nuxtは、サーバーとクライアントの両方の環境で同型(またはユニバーサル)コードを実行できるフレームワークです。$fetch関数
を使用してVueコンポーネントのsetup関数でデータ取得を実行すると、データが2回取得される可能性があります。1回目はサーバーで(HTMLをレンダリングするため)、2回目はクライアントで(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 == null">
No data
</div>
<div v-else>
<form @submit="handleFormSubmit">
<!-- form input tags -->
</form>
</div>
</template>
上記の例では、useFetch
はリクエストがサーバーで発生し、ブラウザに適切に転送されることを保証します。$fetch
にはそのようなメカニズムがなく、リクエストがブラウザからのみ行われる場合はより適切なオプションです。
サスペンス
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
のみを使用すると、ネットワーク呼び出しの重複排除とナビゲーションの防止が提供されませんのでご注意ください。$fetch
はクライアント側のインタラクション(イベントベース)に使用するか、初期コンポーネントデータの取得時にuseAsyncData
と組み合わせて使用することをお勧めします。クライアントヘッダーをAPIに渡す
サーバーサイドレンダリング中、$fetch
リクエストはサーバー内で「内部的に」行われるため、ユーザーのブラウザクッキーは含まれません。
useRequestHeaders
を使用して、サーバーサイドからAPIにクッキーにアクセスし、プロキシすることができます。
以下の例では、APIエンドポイントがユーザーによって最初に送信されたのと同じcookie
ヘッダーにアクセスできるように、同型$fetch
呼び出しにリクエストヘッダーを追加します。
<script setup lang="ts">
const headers = useRequestHeaders(['cookie'])
async function getCurrentUser() {
return await $fetch('/api/me', { headers: headers.value })
}
</script>
host
、accept
content-length
、content-md5
、content-type
x-forwarded-host
、x-forwarded-port
、x-forwarded-proto
cf-connecting-ip
、cf-ray
useRequestFetch
を使用して、ヘッダーを呼び出しに自動的にプロキシすることもできます。::useFetch
useFetch
コンポーザブルは、setup関数で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
をご覧ください。)useFetch
コンポーザブルが適切でない場合もあります。たとえば、CMSやサードパーティが独自のクエリレイヤーを提供する場合などです。この場合、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>
戻り値
useFetch
と useAsyncData
は、以下に示す同じ戻り値を持ちます。data
: 渡された非同期関数の結果。refresh
/execute
:handler
関数によって返されたデータを更新するために使用できる関数。clear
:data
をundefined
に設定し、error
をnull
に設定し、status
をidle
に設定し、現在保留中のリクエストをすべてキャンセルマークを付けるために使用できる関数。error
: データ取得に失敗した場合のエラーオブジェクト。status
: データリクエストのステータスを示す文字列("idle"
、"pending"
、"success"
、"error"
)。
data
、error
、および status
は、<script setup>
内で .value
を使用してアクセスできる Vue ref です。refresh
が完了するまで、再度実行できません。server: false
を使用した場合)、データはハイドレーションが完了するまで取得されません。これは、クライアント側で useFetch
を await したとしても、<script setup>
内の data
は null のままになることを意味します。オプション
useAsyncData
と useFetch
は同じオブジェクト型を返し、共通のオプションセットを最後の引数として受け取ります。これらは、ナビゲーションのブロック、キャッシング、または実行など、コンポーザブルの動作を制御するのに役立ちます。遅延読み込み (Lazy)
デフォルトでは、データ取得コンポーザブルは、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
コンポーザブルは、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 }))
}
})
pick
と transform
のどちらも、不要なデータの最初のフェッチを妨げません。ただし、サーバーからクライアントに転送されるペイロードへの不要なデータの追加は防ぎます。キャッシングと再フェッチ
キー
useFetch
と useAsyncData
は、同じデータの再フェッチを防ぐためにキーを使用します。useFetch
は、提供された URL をキーとして使用します。あるいは、最後の引数として渡されるoptions
オブジェクトにkey
値を指定することもできます。useAsyncData
は、最初の引数が文字列の場合、それをキーとして使用します。最初の引数がクエリを実行するハンドラー関数である場合、useAsyncData
のインスタンスのファイル名と行番号を一意に識別するキーが自動的に生成されます。
useNuxtData
を使用できます。更新と実行
データを手動でフェッチまたは更新する場合は、コンポーザブルによって提供される 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)
アプリケーション内の他のリアクティブな値が変更されるたびにフェッチ関数を再実行するには、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>
<script setup lang="ts">
const id = ref(1)
const { data, error, refresh } = await useFetch(`/api/users/${id.value}`, {
watch: [id]
})
</script>
計算された URL
リアクティブな値から URL を計算し、それらが変更されるたびにデータを更新する必要がある場合があります。複雑な処理をする代わりに、各パラメーターをリアクティブな値として添付できます。Nuxt は自動的にリアクティブな値を使用し、変更されるたびに再フェッチします。<script setup lang="ts">
const id = ref(null)
const { data, status } = useLazyFetch('/api/user', {
query: {
user_id: id
}
})
</script>
<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は含まれず、フェッチレスポンスからのCookieも渡されません。ただし、サーバーで useFetch
を呼び出すと、Nuxt は useRequestFetch
を使用して、ヘッダーとCookieをプロキシします(host
のように転送されないことを意図したヘッダーを除く)。SSR レスポンスでのサーバーサイド API 呼び出しからの Cookie の渡す
内部リクエストからクライアントへのCookieを反対方向に渡す/プロキシする必要がある場合は、自分で処理する必要があります。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>
Options 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 3 で 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(サーバー・セント・イベント)の消費
EventSource
または VueUse のコンポーザブル useEventSource
を使用できます。// 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)
}