TanStack Query を Vue で動かしてみた

# 前書き
これは弁護士ドットコム [Advent Calendar 2022](https://qiita.com/advent-calendar/2022/bengo4com) の 21 日目の記事です🗓

何かとよく耳にする TanStack Query ですが、最近 Vue 対応したとのこと。
割と最近リリースされたようで、ググっても Vue で使っている記事が出てこなかったので実際に動かしてみたといった内容です。

# TanStack Query とは？
https://tanstack.com/query/v4

リファレンスのトップページには以下のように記述されている。

> Powerful asynchronous state management for TS/JS, React, Solid, Vue and Svelte

「TS/JS、React、Solid、Vue、Svelteのための強力な非同期状態管理」 と書かれている。

更にリファレンスの Overview には、

> React Query is often described as the missing data-fetching library for React, but in more technical terms, it makes fetching, caching, synchronizing and updating server state in your React applications a breeze.

要約すると、「アプリケーションでのサーバー状態の取得、キャッシュ、同期、および更新を簡単に行うことができる。」とのこと。
※React Query と記載されているが、最近追加された Vue Query も機能は同じ模様。

普段 Vue を触っている人（自分含め）は馴染みがない人も多いと思うので、 Vue Query を実際に動かしてみます。

# 触ってみる

サンプルコードはこちら https://codesandbox.io/s/tender-bohr-rvphoz

サンプルコードではAPIの代わりにモックを使って実装しています。
サンプルコード上のコードは[リファレンス](https://tanstack.com/query/v4/docs/vue/quick-start)に記載されたコードを一部引用しております。

## インストール

``` zsh
% npm i @tanstack/vue-query
```

## 設定

[リファレンス](https://tanstack.com/query/v4/docs/vue/overview)に掲載されるサンプルコードを動かそうとすると...
以下のランタイムエラーが発生。

```
Uncaught Error: No 'queryClient' found in Vue context, use 'VueQueryPlugin' to properly initialize the library.
```

ドキュメントには説明がなさそう（2022/12/19 現在）だが `VueQueryPlugin` を使ってくださいといわれているので設定してあげる必要がある模様。

main.js に以下を追加。

``` main.js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

//追加
import { VueQueryPlugin } from '@tanstack/vue-query';

// VueQueryPlugin を設定
createApp(App).use(VueQueryPlugin).mount('#app');
```

*12/20追記: VueQueryPlugin の設定関連リファレンスに追加されてました。👏 https://tanstack.com/query/v4/docs/vue/installation

# データの取得
データ取得には、[useQuery](https://tanstack.com/query/v4/docs/react/reference/useQuery) を使います。

``` App.vue
<script setup>
import { useQuery } from "@tanstack/vue-query";
import axios from "axios";

const getTodos = async () => {
  const { data } = await axios.get("/todos");
  return data;
};

const { isLoading, isError, data, error } = useQuery(["todos"], () =>
  getTodos()
);
</script>

<template>
  <span v-if="isLoading">Loading...</span>
  <span v-else-if="isError">Error: {{ error.message }}</span>
  <ul v-else>
    <li v-for="todo in data" :key="todo.id">{{ todo.title }}</li>
  </ul>
</template>
```

ローディング、エラー、取得したデータの表示をかなりコンパクトに書ける印象を受けました。

`useQuery` に `["todos"]` が設定されておりますが、こちらは [Query Key](https://tanstack.com/query/v4/docs/react/guides/query-keys) となり、 Cache を管理するためのラベルで、固有なものを設定してあげる必要があります。後程、 データ登録後の再取得 をする際に使われます。

更に `useQuery` にはオプションが用意されているのでいくつか設定してみたいと思います。

今回設定するオプションは以下の3つです。
- `onSuccess`: リクエストの成功時に実行される
- `onError`: リクエストの失敗時に実行される
- `onSettled`: リクエストの成功、失敗を問わず最後に実行される

``` js
const { isLoading, isError, data, error } = useQuery(
  ["todos"],
  () => getTodos(),
  // 以下追加したオプション
  {
    onSuccess: () => console.log("success: getTodos"),
    onError: () => alert("error"),
    onSettled: () => console.log("settled: getTodos"),
  }
);
```

Tanstack Query では、`then`、`catch`、`finally`と同様の記述がこれらのオプションを使うことで書くことができます。

その他オプションもまだまだあるので、気になる方はリファレンスをご覧になってみてください。https://tanstack.com/query/v4/docs/react/reference/useQuery

# データの登録
続いてデータの登録です。
データの登録には [useMutation](https://tanstack.com/query/v4/docs/react/reference/useMutation) を使います。
todo に "Do Laundry" を登録できるように実装してみます。

``` App.vue
<script setup>
import { useQuery, useMutation } from "@tanstack/vue-query";
import axios from "axios";

const getTodos = async () => {
  const { data } = await axios.get("/todos");
  return data;
};

const { isLoading, isError, data, error } = useQuery(["todos"], () =>
  getTodos(),
  {
    onSuccess: () => console.log("success: getTodos"),
    onError: () => alert("error"),
    onSettled: () => console.log("settled: getTodos")
  }
);

const postTodo = async (newTodo) => await axios.post("/todos", newTodo);

const { mutate } = useMutation({
  mutationFn: (newTodo) => postTodo(newTodo),
  onSuccess: () => console.log("success: postTodo"),
  onError: () => alert("error"),
  onSettled: () => console.log("settled: postTodo"),
});

const onButtonClick = () => {
  mutate({
    id: Date.now(),
    title: "Do Laundry",
  });
};
</script>

<template>
  <span v-if="isLoading">Loading...</span>
  <span v-else-if="isError">Error: {{ error.message }}</span>
  <ul v-else>
    <li v-for="todo in data" :key="todo.id">{{ todo.title }}</li>
  </ul>
  <button @click="onButtonClick">Add Todo</button>
</template>
```

使い方は`useQuery` と大きく変わりません。オプションの設定方法も同じです。
`useMutation` で定義した mutate を実行することで、 mutationFn に定義した関数を実行しています。

ここまででデータの登録ができましたが、このままだと新たに登録した todo が画面上に反映されないのでデータを取得できるよう実装してみます。

# データ登録後の再取得

データの再取得には[useQueryClient](https://tanstack.com/query/v4/docs/react/reference/useQueryClient) を使います。

``` vue
<script setup>
import { useQueryClient, useQuery, useMutation } from "@tanstack/vue-query";
import axios from "axios";

const queryClient = useQueryClient();

const getTodos = async () => {
  const { data } = await axios.get("/todos");
  return data;
};

const { isLoading, isError, data, error } = useQuery(
  ["todos"],
  () => getTodos(),
  {
    onSuccess: () => console.log("success: getTodos"),
    onError: () => alert("error"),
    onSettled: () => console.log("settled: getTodos"),
  }
);

const postTodo = async (newTodo) => {
  await axios.post("/todos", newTodo);
};

const { mutate } = useMutation({
  mutationFn: (newTodo) => postTodo(newTodo),
  onSuccess: () => {
    queryClient.invalidateQueries(["todos"]);
  },
  onError: () => alert("error"),
  onSettled: () => console.log("settled: postTodo"),
});

const onButtonClick = () => {
  mutate({
    id: Date.now(),
    title: "Do Laundry",
  });
};
</script>

<template>
  <span v-if="isLoading">Loading...</span>
  <span v-else-if="isError">Error: {{ error.message }}</span>
  <ul v-else>
    <li v-for="todo in data" :key="todo.id">{{ todo.title }}</li>
  </ul>
  <button @click="onButtonClick">Add Todo</button>
</template>
```

`useQueryClient()` で queryClient を呼び出します。queryClient は [invalidateQueries](https://tanstack.com/query/v4/docs/react/guides/query-invalidation#query-matching-with-invalidatequeries) という再 fetch するためのメソッドを持っています。
こちらを実行することで既存の`Query Key`として登録されている `todos` のクエリが最新のものではないことを知らせ、`useQuery` が 再 fetch を行います。

本記事では詳しくは書きませんが、こちらを上手く活用することでクエリの呼び出しを毎回行うことなく Cache からデータを呼び出すことでができるようになります。

# まとめ
非同期処理の実装をするに当たってかなり有用なライブラリという印象を受けました。
非同期処理の実装がコンパクトになるのと、 Cache 周りの実装がかなり楽にできるといった点がかなり良いと思いました。
~~期日ギリギリに書いた記事なので、~~  Cache 周りについて詳しいことを書くことができませんでしたが、興味がある方はその辺りも調べていただけると良さそうです。

明日は @naito1224 さんの記事となります。お楽しみに！🎅🎄