Nuxt3のuseFetch、useAsyncDataはuseSWRの代わりにはならない
useFetchとuseAsyncData
Nuxt3では、APIリクエストを行うユーティリティ関数として、useFetchとuseAsyncDataが用意されています。
useFetchはuseAsyncDataをシンプルにしたもので基本的に同じのため、useAsyncDataで考えます。
記事の趣旨
useAsyncDataは、Reactでよく使われる useSWR や useQuery の代わりになるのかと思っていましたが、そうではないようでした。
- (1) リクエストのキャッシュ機能はない
- (2) 取得用の用途がメインで、POSTなどの用途には向かない
本記事では、上記の例と、代替として vue-query について少し紹介します。
(1) リクエストのキャッシュ機能はない
記事の一覧を取得・表示する例を考えます。
SSRの場合
<script setup lang="ts">
type Article = { id: number; title: string; body: string };
const { data, pending } = await useAsyncData<Article[]>("articles", () =>
$fetch("http://localhost:3000/articles")
);
</script>
<template>
<h1>記事一覧</h1>
<div>pending: {{ pending }}</div>
<ul>
<li v-for="article in data" :key="article.id">
id: {{ article.id }} | title: {{ article.title }} | body:
{{ article.body }}
</li>
</ul>
</template>
(分かりにくいですが、ブラウザのURL直打ちで遷移しています)
このぺージを初回表示すると、サーバーサイドで記事一覧の取得まで完了したHTMLが返され、pendingがtrueになることはありません。
当然リロードするたびにAPIリクエストが走ります。
CSRの場合
初回表示ではなく、クライアント側のrouterにより遷移した場合、 lazy
オプションによって挙動が変わります。
lazy: falseの場合
まず、上記の通り lazy
オプションなしだと、SSRの場合と同様にAPIリクエストが完了するのを待ってから、ページ遷移が完了します。(pendingがtrueになることはありません。)
lazy: trueの場合
以下のように lazy: true
に設定すると、
const { data, pending } = await useAsyncData<Article[]>(
"articles",
() => $fetch("http://localhost:3000/articles"),
{ lazy: true }
);
APIリクエスト完了を待たずに遷移して、pendingがtrueで表示されます。
リクエストが完了すると、pendingがfalseとなり、データが表示されます。
もう一度ページ遷移した時
この状態で、一度別のページに遷移した後もう一度戻ってくると、以下のような挙動になります。
- 以前のリクエストの結果を表示したまま、裏でAPIリクエストが走る(pending: true)
- APIリクエストが完了すると、データを新しいものに置き換えてpending: falseとなる
useAsyncDataの第一引数に渡す key
が同じであれば、このように以前の結果をキャッシュとして表示してくれるようです。
キャッシュの制御について
しかし、裏でAPIリクエストが走るのを制御する方法はなさそうで、useSWRのように指定時間以内であればAPIリクエスト自体がされないようにするなどはできなさそうです。
また、ログアウト時に表示キャッシュを一括で無効にするなどもできなさそうです。
重複排除について
useAsyncDataのドキュメント に、以下のように同じKeyであればデータフェッチの重複排除がされるとあるのですが、
key: a unique key to ensure that data fetching can be properly de-duplicated across requests.
上記のpageをコンポーネント化して、以下のように並べてみても、2回APIリクエストが走っていて、 Next13のfetch のように重複排除されるということはなさそうでした。
<template>
<Articles />
<Articles />
</template>
(2) POSTの処理には向かない
useAsyncDataは、以下のようにPOSTの処理も行うことはできます。
<script setup lang="ts">
const formValue = ref({ title: "", body: "" });
const { data, pending, execute } = await useAsyncData(
"new-article",
() =>
$fetch("http://localhost:3000/articles", {
method: "POST",
body: {
article: { title: formValue.value.title, body: formValue.value.body },
},
}),
{ immediate: false }
);
const onSubmit = async (e: Event) => {
e.preventDefault();
await execute();
};
</script>
<template>
<h1>記事作成</h1>
<v-form @submit="onSubmit">
<v-text-field v-model="formValue.title" label="タイトル" />
<v-textarea v-model="formValue.body" label="内容" />
<v-btn type="submit">送信</v-btn>
</v-form>
<div>pending: {{ pending }}</div>
<div>data: {{ data }}</div>
</template>
ただ、上記キャプチャのように、送信中以外も最初から pending: true
になっていたり、APIレスポンスは execute()
の戻り値ではなく data
から受け取る必要があったり、使い勝手が悪い印象でした。
vue-query
tanstackのreact-queryのVue版として、 vue-query というライブラリがあります。
こちらは、react-queryと同じ使用感で、上記に挙げたようなリクエストのキャッシュ機構を利用したり、useMutationでPOST処理までスムーズに行うことができそうです。
参考記事:
また、少し触った程度ですが、SSRにも対応しているようで、useAsyncDataを使わず、vue-queryに一本化するという選択肢もありそうです。
最後に
筆者はまだNuxt3の経験が浅いため、間違っていることがありましたらご指摘ください🙇♂️
その他、Nuxt3におけるデータフェッチやPOST処理の良い方法や、veu-queryの活用事例がありましたら、ご教授いただけますと幸いです🙇♂️
Discussion