BLOG

ブログ

Vue3 と Tanstack Query でのAPI結合について(useQuery編)

こちらはゆるWeb勉強会@札幌 2022年アドベントカレンダー」の24日目の記事です。

フロントエンドエンジニアのハコザキです。

今回はVueのAPI結合時のデータ取得について、
Tanstack Queryを使ってみたのでご紹介します!

この記事は、
-> Reactでの実装案件でTanstack Queryを知る
-> 非同期の状態管理やキャッシュの面ですごく便利、コードも見やすい!
-> Vueにも対応してるっぽいので試してみたら面白そう!
と思い書きました。

Tanstack Queryとは?

非同期データを管理してキャッシュするためのライブラリです。
また、ローディングやエラーの状態なども取得できるため、別途状態管理しなくて済みます。
シンプルにAPI結合処理を書くことができ、コードが見やすいといったメリットもあります。
公式サイトでは以下のように紹介されています。※ DeepLで翻訳

— — — — —
Powerful asynchronous state management for TS/JS, React, Solid, Vue and Svelte
“TS/JS、React、Solid、Vue、Svelteのための強力な非同期状態管理”

Toss out that granular state management, manual refetching and endless bowls of async-spaghetti code. TanStack Query gives you declarative, always-up-to-date auto-managed queries and mutations that directly improve both your developer and user experiences.
“きめ細かな状態管理、手動での再フェッチ、延々と続く非同期スパゲッティ・コードを捨てましょう。TanStack Queryは、宣言的で常に最新の自動管理されたクエリとミューテーションを提供し、開発者とユーザーの両方のエクスペリエンスを直接向上させます。”
— — — — —

React Queryだったのが、VueやSvelteなどにも対応されるようになり、
Tanstack Query(v4)と名称が変更されました。

Reactで使う場合は tanstack/react-query
Vueの場合は、tanstack/vue-query となります。

コンテンツの取得系はuseQuery、
更新系はuseMutationなどといったフックが用意されていて、
今回はuseQueryを使ってみましたのでご紹介します。

また、以下のように状況に応じて様々なフックが用意されています。
詳しくはこちら

export * from '@tanstack/query-core';
export { useQueryClient } from './useQueryClient';
export { VueQueryPlugin } from './vueQueryPlugin';
export { QueryClient } from './queryClient';
export { QueryCache } from './queryCache';
export { MutationCache } from './mutationCache';
export { useQuery } from './useQuery';
export { useQueries } from './useQueries';
export { useInfiniteQuery } from './useInfiniteQuery';
export { useMutation } from './useMutation';
export { useIsFetching } from './useIsFetching';
export { useIsMutating } from './useIsMutating';
export { VUE_QUERY_CLIENT } from './utils';
export type { UseQueryOptions, UseQueryReturnType, UseQueryDefinedReturnType, } from './useQuery';
export type { UseInfiniteQueryOptions, UseInfiniteQueryReturnType, } from './useInfiniteQuery';
export type { UseMutationOptions, UseMutationReturnType } from './useMutation';
export type { UseQueriesOptions, UseQueriesResults } from './useQueries';
export type { MutationFilters } from './useIsMutating';
export type { QueryFilters } from './useIsFetching';
export type { VueQueryPluginOptions } from './vueQueryPlugin';

また、Tanstack と呼ばれるプロジェクト群には、今回ご紹介した Tanstack Query の他に
tableUIの実装に便利な TanStack Table や、
ルーティングライブラリの TanStack Router(2022年12月時点ではbeta)など様々なオープンソースなライブラリがあります!

Vue3 + Vite + Tanstack Query でのデータ取得例

今回はVue3 + ViteでTanstack Queryを使い、
WP REST APIからWordPressの記事を取得してみたいと思います。
※ 取得して表示するだけなのでとてもシンプルです

表示例

NodeとYarnのバージョンは以下の通りです。

Node v16.15.1
Yarn v1.22.19

続いて、yarn create vite でプロジェクトを作成します。
--template vue-ts でサポートされたテンプレートを使うことができます。

yarn create vite vue3-vite-tanstack-app --template vue-ts
cd vue3-vite-tanstack-app
yarn
yarn dev

続いて、Tanstack Queryとaxiosを追加します。
※ fetchでも実装できますが、今回はaxiosを使います。

yarn add @tanstack/vue-query axios

ライブラリの各バージョンは以下のようになります。

{
  "name": "vue3-vite-tanstack-app",
  "private": true,
  "version": "0.0.1",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@tanstack/vue-query": "^4.20.0",
    "axios": "^1.2.1",
    "vue": "^3.2.45",
    "vue-router": "^4.1.6"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^4.0.0",
    "typescript": "^4.9.3",
    "vite": "^4.0.0",
    "vue-tsc": "^1.0.11"
  }
}

続いて、src/main.ts を以下の記述にしてTanstackQueryを使えるようにします。

import { createApp } from "vue";
import App from "./App.vue";

import { VueQueryPlugin } from "@tanstack/vue-query";

const app = createApp(App);
app.use(VueQueryPlugin);

app.mount("#app");

composableフォルダを作成し、
useQueryをラップしたカスタムフックを作成します。

import { useQuery } from "@tanstack/vue-query";
import axios from "axios";

// WPのカスタムポストタイプ
type CustomPostType = "news" | "blog";
// WPでの記事取得オプション
type QueryOptionType = {
  perPage?: number; // 1ページあたりの取得件数
  page?: number; // 取得ページ番号
};

// useQueryをラップ
export default (
  cpt: CustomPostType,
  { perPage = 10, page = 1 }: QueryOptionType
) => {
  return useQuery({
    queryKey: ["post", cpt, `perPage${perPage}`, `page${page}`],
    queryFn: () =>
      axios.get(`${import.meta.env.VITE_BASE_URL}/wp-json/wp/v2/${cpt}`, {
        params: {
          per_page: perPage,
          page,
        },
      }),
    select: (response) => response.data,
  });
};

queryKey では、クエリを識別するための一意な値を、配列形式で設定する必要があります。
stringで指定できますが、内部で配列として変換されるため、基本的に配列指定します。
配列の値が変更になったタイミングで、再度APIが実行されるようになります。
今回の例だと、WPのカスタムポストタイプや perPagepage などのクエリが変更になったタイミングで queryFn が実行されるようになります。

queryFn では、非同期で実行させる関数を定義します。
認証が必要なAPIを実行する場合は、リクエスト時に認証情報を付与する必要があります。

select では、queryFn で実行した非同期関数のレスポンスから必要なものだけ返すように設定します。 
今回は、axiosでのレスポンスからdata内のみを利用するようにしてます。

表示側はこのようになります。

<script setup lang="ts">
import usePostQuery from "./composables/usePostQuery";
// ブログ記事取得
const postsResponse = usePostQuery("blog", {
  perPage: 3,
  page: 1,
});
const { data: posts, isLoading, isError } = postsResponse;
</script>

<template>
  <p v-if="isLoading">取得中です</p>
  <p v-if="isError">再度お試しください</p>
  <ul>
    <li v-for="post in posts" :key="`post-${post.id}`">
      <p>{{ post.title.rendered }}</p>
    </li>
  </ul>
</template>
import usePostQuery from "./composables/usePostQuery";
// ブログ記事取得
const postsResponse = usePostQuery("blog", {
  perPage: 3,
  page: 1,
});
const { data: posts, isLoading, isError } = postsResponse;

先程作った usePostQuery を利用して記事取得を行います。
data には非同期で取得した値が入っており、
isLoadingisError を用いて表示の切り替えを行いました。

useQueryの戻り値

リクエストを最小限にする

yarn devでアプリケーション起動した状態で、新しいタブに遷移し、
再度アプリケーションのタブに戻るとリクエストが投げられています。

デベロッパーツールのネットワークでリクエストを確認

これはTanstackQueryがデータが古くなったと判断し、再取得処理を行っているみたいです。

キャッシュ戦略はプロジェクトごとに変わると思いますが、
今回はただ記事を取得するだけなので、リフェッチする必要がありません。
なので、再取得しない設定をしてみます。

キャッシュの設定には、cacheTimeとstaleTimeの 2つのプロパティがあります。

cacheTimeはデータを取得しキャッシュする時間、
staleTimeはキャッシュが古くなったと判断する時間です。

cacheTimeを伸ばしても良いですが、
staleTimeを無効化することができるのでuseQueryに以下のように追記します。

useQuery({
  queryKey: [...],
  queryFn: () => {}
  select: () => {},
  staleTime: Infinity, // 追加
});

staleTimeにInfinityを設定することで、
リフェッチしないように常に新しいキャッシュを保持することができます。

タブを切り替えてもリクエストが投げられていない

Query Keyについて

基本的な取得処理はできたので、Query Keyの動作確認をしてみました。

Query Keyで指定した配列の値が変更になったタイミングでAPIリクエストが走っています。
新しいページ番号を指定した場合は “取得中です” と表示され、
すでにリクエスト投げているページ番号を指定した場合はそのまま表示されるようになってます。
※ 動作確認のため、意図的にネットワーク速度を遅くしてます。

おわりに

今回はVue3のAPI結合例について、Tanstack Queryを紹介しました。
自分自身、Reactの実装でこちらのライブラリを利用した際
シンプルに非同期な結合処理を書くことができ、すごく良いなと思いVue3で実装してみました。

今回はuseQueryだけでしたが useMutation でのフォーム送信なども
同様にシンプルに実装できるので、そちらは次回書こうと思います!