最終更新日: 2023年08月15日

データの取得(Data fetching)

Nuxtには、ブラウザやサーバー環境でデータの取得を行うための2つのコンポーザブルと組み込みライブラリが用意されています。
用意されているものは以下の通りです:

  • useFetch: ブラウザ環境でデータの取得を行うためのコンポーザブル
  • useAsyncData: サーバー環境でデータの取得を行うためのコンポーザブル
  • $fetch: ブラウザまたはサーバー環境でデータの取得を行うための組み込みライブラリ

これらのコンポーザブルと組み込みライブラリを組み合わせることで、クロス環境(サーバーとブラウザ間)の互換性が確保され、効率的なキャッシュが行われ、重複したネットワークリクエストが回避されます。

useFetchは、コンポーネントのセットアップ関数でデータ取得を処理するための最も直感的な方法です。

一方、ユーザーの操作に基づいてネットワークリクエストを行いたい場合は、ほとんどの場合、$fetchが適切なハンドラです。

もっと細かい制御が必要な場合は、useAsyncData$fetchを独立して使用することもできます。

これらの2つのコンポーザブルは、共通のオプションとパターンを共有しており、最後のセクションで詳しく説明します。

特定のコンポーザブルを使用する理由は何でしょうか?

Nuxtのようなフレームワークを使用する場合、クライアントとサーバーの環境の両方で呼び出しとページのレンダリングを行う必要があり、いくつかの課題に取り組む必要があります。そのため、Nuxtはクエリをラップするためのコンポーザブルを提供しています。

ネットワーク呼び出しの重複

useFetchuseAsyncDataのコンポーザブルは、サーバー上でAPIコールが行われた後、データが適切にクライアントに転送されることを保証します。 このJavaScriptオブジェクトは、useNuxtApp().payloadを介してアクセスでき、ブラウザでコードが実行される際に同じデータを再取得するのを避けるために使用されます。

InfoNuxt DevToolsを使用して、このデータをペイロードタブで確認することができます。

効果的なキャッシュ

useFetchuseAsyncDataは、APIのレスポンスをキャッシュするためにキーを使用し、API呼び出しをさらに減らすことができます。 キャッシュを無効化する方法については後述で説明します。

一時停止

NuxtはVueの<Suspense>コンポーネントを内部で使用しており、すべての非同期データがビューで利用可能になるまでのナビゲーションを防止します。 データの取得に関連するコンポーザブルを使用することで、パフォーマンスに最適な方法を個別の呼び出しに応じて選択することができます。

useFetch

useFetchは、データの取得を行うための最も直接的な方法です。 これは、useAsyncDataコンポーザブルと$fetchユーティリティをラップしたものです。

  • app.vue
  • ts
<script setup>
  const { data: count } = await useFetch('/api/count')
</script>

<template>
  Page visits: {{ count }}
</template>

Info 詳細は「Docs > API > コンポーザブル > Use Fetch」を参照してください。

Infoまたは、「Docs > Examples > Features > Data Fetching」から、実際のサンプルを確認しながら学ぶことができます。

$fetch

ofetchライブラリは、fetch APIをベースにしており、それに便利な機能を追加しています:

  • ブラウザ、Node、ワーカー環境で同じように動作します
  • 自動的なレスポンスの解析
  • エラーハンドリング
  • 自動リトライ
  • インターセプター

Infoofetchの完全なドキュメントを読む

ofetchはNuxtによって自動的にインポートされ、useFetchコンポーザブルで使用されます。

または、$fetchエイリアスを使用してアプリケーション全体で使用することもできます。

  • app.vue
  • ts
const users = await $fetch('/api/users').catch((error) => error.data)

Info 詳細は「Docs > API > Utils > Dollarfetch」を参照してください。

useAsyncData

useFetchはURLを受け取り、そのデータを取得します。一方、useAsyncDataはより複雑なロジックを持つ場合があります。 useFetch(url)は、ほとんどの一般的な使用例に対する開発者体験を向上させるためのシンタックスシュガーであり、ほぼuseAsyncData(url, () => $fetch(url))と同等です。

useFetchコンポーザブルが適切でない場合もあります。例えば、CMSやサードパーティが独自のクエリレイヤーを提供している場合などです。 このような場合、useAsyncDataを使用して呼び出しをラップし、コンポーザブルが提供する利点を保持することができます。

  • ts
const { data, error } = await useAsyncData('users', () => myGetFunction('users'))

Info 詳細は「Docs > API > Composables > Use Async Data」を参照してください。

オプション

useAsyncDatauseFetchは、同じオブジェクト型を返却し、共通のオプションを使用することができます。 これらのオプションを使用することで、ナビゲーションのブロック、キャッシュ、実行など、コンポーザブルの動作を制御することができます。

Lazy

デフォルトでは、データ取得のコンポーザブルは非同期関数の解決が完了するまで、VueのSuspenseを使用して新しいページへの遷移を待機します。ただし、lazyオプションを使用すると、クライアントサイドのナビゲーションではこの機能を無視することができます。その場合、pendingを使用してローディング状態を手動で処理する必要があります。

  • app.vue
  • ts
<template>
  // ローディング状態を処理する
  <div v-if="pending">
    Loading ...
  </div>
  <div v-else>
    <div v-for="post in posts">
      // 何かしらのデータの表示
    </div>
  </div>
</template>

<script setup>
  const { pending, data: posts } = useFetch('/api/posts', {
    lazy: true
  })
</script>

代わりに、useLazyFetchuseLazyAsyncDataを使用することもできます。これらは同じ機能を提供する便利な方法です。

  • app.vue
  • ts
const { pending, data: posts } = useLazyFetch("/api/posts");

Info 詳細は「Docs > API > Composables > Use Lazy Fetch」を参照してください。

Info 詳細は「Docs > API > Composables > Use Lazy Async Data」を参照してください。

Client-only fetching

デフォルトでは、データをフェッチするコンポーザブルは、Vue の Suspense を使用して新しいページに移動する前に、非同期関数の解決を待機します。この機能は、lazy オプションを使用したクライアント側ナビゲーションでは無視できます。 その場合、pendingの値を使用して読み込み状態を手動で処理する必要があります。

  • app.vue
  • ts
/* この呼び出しはクライアント側でのみ実行されます。 */
const { pending, data: posts } = useFetch("/api/comments", {
  lazy: true,
  server: false,
});

ペイロードのサイズを最小化する

pickオプションは、コンポーザブルから返されるフィールドを選択することで、HTMLドキュメントに格納されるペイロードのサイズを最小限に抑えるのに役立ちます。

  • app.vue
  • ts
<script setup>
/* テンプレートで使用されるフィールドのみ選択してください */
const { data: mountain } = await useFetch(
    '/api/mountains/everest',
    { pick: ['title', 'description'] }
    )
</script>

<template>
  <h1>{{ mountain.title }}</h1>
  <p>{{ mountain.description }}</p>
</template>

複数のオブジェクトをより詳細に制御またはマップする必要がある場合は、transform関数を使用してクエリの結果を変更できます。

  • ts
const { data: mountains } = await useFetch("/api/mountains", {
  transform: (mountains) => {
    return mountains.map((mountain) => ({
      title: mountain.title,
      description: mountain.description,
    }));
  },
});

キャッシュと再取得

Keys

useFetchuseAsyncDataは、同じデータを再度取得しないためにキーを使用します。

  • useFetchでは、提供されたURLをキーとして使用します。 また、オプションオブジェクトとして最後の引数として渡されたキー値を使用することもできます。
  • useAsyncDataでは、最初の引数が文字列である場合、それをキーとして使用します。 最初の引数がクエリを実行するハンドラ関数である場合、useAsyncDataのインスタンスのファイル名と行番号に固有のキーが自動的に生成されます。

リフレッシュと実行

データを手動でフェッチまたはリフレッシュしたい場合は、コンポーザブルが提供するexecuteまたはrefresh関数を使用します。 (executeはrefreshとまったく同じ方法で機能しますが、immediate: falseの場合にセマンティックになります。)

  • app.vue
  • ts
<script setup>
  const { data, error, execute, refresh } = await useFetch('/api/users')
</script>

<template>
<div>
  <p>{{ data }}</p>
  <button @click="refresh">Refresh data</button>
</div>
</template>

監視(Watch)

他のリアクティブな値が変更されるたびにフェッチ関数を再実行するには、watchオプションを使用します。

  • ts
<script setup>
  const { data, error, refresh } = await useFetch('/api/users', {
    /* IDを変更すると、再取得がトリガーされます */
    watch: [id]
  })

  const id = ref(1)
</script>

ヘッダーとクッキーのパッシング

ブラウザで$fetchを呼び出すときは、cookieなどのユーザーのヘッダーがAPIに直接送信されます。 しかし、サーバーサイドレンダリング中には、$fetchリクエストはサーバー内で実行されるため、ユーザーのブラウザクッキーは含まれず、fetchのレスポンスからのクッキーも渡されません。

クライアントのヘッダーをAPIに渡す

サーバーサイドからAPIにクッキーをアクセスおよびプロキシするために、useRequestHeadersを使用することができます。

以下の例では、リクエスト ヘッダーを同形$fetch呼び出しに追加して、API エンドポイントがユーザーによって最初に送信されたのと同じ Cookieヘッダーにアクセスできるようにします。

  • ts
<script setup>
  const headers = useRequestHeaders(['cookie'])
  const { data } = await useFetch('/api/me', { headers })
</script>

サーバーサイドのAPI呼び出しでクッキーをSSRレスポンスに含める

内部リクエストからクライアントに戻るなど、逆方向にCookieを渡したりプロキシしたりする場合は、これを自分で処理する必要があります。

  • composables/fetch.ts
  • ts
<script setup>
  import { appendResponseHeader, H3Event } from 'h3'

  export const fetchWithCookie = async (event: H3Event, url: string) => {
    const res = await $fetch.raw(url)
    const cookies = (res.headers.get('set-cookie') || '').split(',')
    for (const cookie of cookies) {
      appendResponseHeader(event, 'set-cookie', cookie)
    }
    return res._data
  }
</script>
  • ts
<script setup lang="ts">
  // この「composable(再利用可能なコードの部品)」は、
  // 自動的にクライアントにクッキーを渡します。
  const event = useRequestEvent()
  const result = await fetchWithCookie(event, '/api/with-cookie')
  onMounted(() => console.log(document.cookie))
</script>

Options API サポート

Nuxt3では、Options API内でasyncDataの取得を行う方法が提供されています。 これを使用するには、コンポーネント定義をdefineNuxtComponentでラップする必要があります。

  • ts
<script>
  export default defineNuxtComponent({
    /* fetchKeyオプションを使用して、一意のキーを提供してください */
    fetchKey: 'hello',
    async asyncData () {
      return {
        hello: await $fetch('/api/hello')
      }
    }
  })
</script>

Info 詳細は「Docs > API > Utils > Define Nuxt Component」を参照してください。

シリアライゼーション

サーバーディレクトリからデータを取得する際、レスポンスはJSON.stringifyを使用して直列化されます。 ただし、直列化はJavaScriptのプリミティブ型に限定されているため、Nuxtは$fetchやuseFetchの戻り値の型を実際の値に合わせるために最善の努力をします。

Info JSON.stringifyの制限について詳しくはこちらをご覧ください。

  • server/api/foo.ts
  • ts
export default defineEventHandler(() => {
  return new Date();
});
  • app.vue
  • vue
<script setup lang="ts">
// dataの型がDateオブジェクトを返したにもかかわらず、文字列として推論されています
const { data } = await useFetch("/api/foo");
</script>

カスタム シリアライゼーション ファンクション

シリアル化の動作をカスタマイズするには、返されたオブジェクトにtoJSON関数を定義します。 toJSONメソッドを定義すると、Nuxtはその関数の返り値の型を尊重し、型の変換を試みることはありません。

  • server/api/bar.ts
  • ts
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;
});
  • app.vue
  • ts
<script setup lang="ts">
  // 推論されるデータ型
  // {
  //   createdAt: {
  //     year: number
  //     month: number
  //     day: number
  //   }
  // }
  const { data } = await useFetch('/api/bar')
</script>

代替のシリアライザを使用する / Using an alternative serializer

現時点では、NuxtはJSON.stringify以外の代替シリアライザをサポートしていません。 ただし、ペイロードを通常の文字列として返し、toJSONメソッドを使用して型の安全性を保つことができます。

以下の例では、シリアライザとしてsuperjsonを使用しています。

  • server/api/superjson.ts
  • ts
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;
});
  • app.vue
  • ts
<script setup lang="ts">
  import superjson from 'superjson'

  // dateは{ createdAt: Date }と推論され、安全にDateオブジェクトのメソッドを使用できます。
  const { data } = await useFetch('/api/superjson', {
    transform: (value) => {
      return superjson.parse(value as unknown as string)
    },
  })
</script>