nuxt logo

ドキュメント翻訳(非公式)

データ取得

Nuxtはアプリケーション内でのデータフェッチを処理するためのcomposablesを提供しています。

Nuxtはブラウザまたはサーバ環境でデータフェッチを行うための2つのcomposablesと組み込みライブラリを備えています: useFetchuseAsyncData そして $fetch

簡単に説明すると:

  • $fetch はネットワークリクエストを行う最もシンプルな方法です。
  • useFetch$fetch をラップして、ユニバーサルレンダリングで一度だけデータをフェッチします。
  • useAsyncDatauseFetch と似ていますが、より細かい制御を提供します。

useFetchuseAsyncData は共通のオプションとパターンを持っており、最後のセクションで詳しく説明します。

useFetchuseAsyncData が必要な理由

Nuxtはサーバーとクライアントの両方の環境でアイソモーフィック(またはユニバーサル)コードを実行するフレームワークです。Vueコンポーネントのセットアップ関数で $fetch 関数 を使用してデータフェッチを行うと、サーバーで一度(HTMLをレンダリングするため)とクライアントで再度(HTMLがハイドレートされるとき)の2回、データがフェッチされる可能性があります。これはハイドレーションの問題、インタラクティブへの到達時間の増加、予測不可能な挙動を引き起こす可能性があります。

useFetchuseAsyncData のcomposablesは、サーバーでAPI呼び出しが行われた場合、データがペイロードを介してクライアントに転送されることを保証することでこの問題を解決します。

ペイロードは、useNuxtApp().payload を通じてアクセス可能なJavaScriptオブジェクトであり、コードがブラウザで実行される際ハイドレーション中に同じデータを再フェッチするのを避けるためにクライアントで使用されます。

Nuxt DevTools を使用して、Payloadタブでこのデータを確認してください。

app.vue
<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 のエイリアスとして自動的にインポートされます。

pages/todos.vue
async function addTodo() {
  const todo = await $fetch('/api/todos', {
    method: 'POST',
    body: {
      // Todoデータ
    }
  })
}

$fetch のみを使用すると、ネットワーク呼び出しの重複排除やナビゲーション防止 が提供されません。:br クライアントサイドのインタラクション(イベントベース)や、コンポーネントの初期データをフェッチする際に useAsyncData と組み合わせて使用することが推奨されます。

こちらも参照 api > utils > dollarfetch

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

サーバー上で useFetch を呼び出すとき、Nuxtは useRequestFetch を使用してクライアントのヘッダーとクッキーをプロキシします(ホストなどの転送されるべきではないヘッダーを除く)。

const { data } = await useFetch('/api/echo');

オプション

useAsyncData および useFetch は同じオブジェクト型を返し、最後の引数として共通のオプションセットを受け入れます。これにより、ナビゲーションのブロック、キャッシュ、実行などのcomposablesの動作を制御できます。

Lazy

デフォルトでは、データ取得composablesはVueのSuspenseを使用して新しいページにナビゲートする前に非同期関数の解決を待ちます。この機能はクライアントサイドのナビゲーションではlazyオプションで無視できます。その場合、status値を使用してロード状態を手動で処理する必要があります。

app.vue
<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>

また、useLazyFetchuseLazyAsyncData を便利な方法として使用することもできます。

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で同じキーを使用する場合、同じdataerrorおよび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 の返り値を実際の値に合わせるために最善を尽くします。

こちらも参照 developer.mozilla.org > en-US > docs > Web > JavaScript > Reference > Global_Objects > JSON > stringify

実装例

server/api/foo.ts
export default defineEventHandler(() => {
  return new Date()
})
app.vue
// `data` の型は Date オブジェクトを返しているにも関わらず文字列と推論される
const { data } = await useFetch('/api/foo')

カスタムシリアライザー関数

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

server/api/bar.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
// `data` の推論された型は
// {
//   createdAt: {
//     year: number
//     month: number
//     day: number
//   }
// }
const { data } = await useFetch('/api/bar')

代替シリアライザーの使用

Nuxt は現在 JSON.stringify 以外のシリアライザーをサポートしていません。しかし、ペイロードを通常の文字列として返し、型の安全性を維持するために toJSON メソッドを利用することができます。

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

server/api/superjson.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
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]);