Reactの最強状態管理ツールreact-queryがメジャーリリースされたぞ！

# react-query とは？

`react-query`とは 2 月 18 日にメジャーリリースされたばかりの React プロジェクトにおける状態管理用のモジュールです。
ネット上に情報がまだ少ないため、一部抜粋しながら使い方と魅力についてまとめていきます。

時間があるときに追加していくのでまだ準備中が多いです。`README`がめちゃくちゃ分かりやすいのでそちらを見たほうが断然早いです。

> tannerlinsley/react-query
> https://github.com/tannerlinsley/react-query

基本的な機能としては、**Hooks**を用いた

- データの取得（サーバの実装は REST でも GraphQL でも何でも ok）
- 自動キャッシュ
- 自動再取得
- データの反映 → データの再取得
- ページネーションにも対応
- リクエストキャンセル

です。
基本的にクエリに任意の`queryKey`を与え、取得したデータを`queryKey`ごとに`global state`に自動でキャッシュしてくれます。

僕が考えるこのモジュールの最大のメリットは、データの取得の API を叩くロジックを一定のルールに沿ってラッピングするため、コードの可読性が上がるという点にあると思います。また、`Suspence`を用いた再取得がとてもわかりやすく書けるので最高です。

以下は`todoId = 1`の`todo`を取得する例ですが、API を叩く関数では promise を返し、使う側は`key`と`todoId`を`queryKey`として配列で渡すだけで、`loading`, `error`, `success`の状態や`error`が発生しているかどうかの`boolean`を簡単に取得できます。

```js
const todoId = 1;
const { status, data, error } = useQuery(["todos", "todoId"], fetchTodoList);

const fetchTodoList = (key, todoId) => {
  return axios.get(`/todos/${todoId}`).then(res => res.data);
};
```

## react-query のインストール

以下のコマンドで追加できます。

```
yarn add react-query
```

DefinitelyTyped が公開されていますが、現在（2020/3/1)はまだ古いバージョンのため、typescriptで利用するのは厳しいようです。

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">For now, no. I&#39;m sorry I can&#39;t give you any TS hugs today.<br><br>I did a TS migration on v0.x, and it went really well, but then I needed to write v1 (which was more than a refactor) and ended up chucking it all out.<br><br>I&#39;ll change some day. But not today.</p>&mdash; Tanner Linsley (@tannerlinsley) <a href="https://twitter.com/tannerlinsley/status/1232786838433042432?ref_src=twsrc%5Etfw">February 26, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

```
yarn add @types/react-query
```

> tannerlinsley/react-query
> https://github.com/tannerlinsley/react-query

## queryKey とは

`queryKey`はこのモジュールの最重要ポイントです。`string` もしくは`string`, `number`の値とそれらを含む`object`の`array`で示されます。
キャッシュの管理を行うために必須な値であり、雰囲気としては`GraphQL`の`query`に似ています。
このモジュールの強みは`queryKey`が以下のように柔軟に設定できることにあります。

### String-Only Query Keys

文字列一つを`queryKey`とするときはこのように書きます。

```js
useQuery('todos', ...)
// queryKey === ['todos']
```

右側のコメントがモジュール内で利用される`queryKey`で、配列型に変換されるようになっています。

### Array Keys

もし、その他の情報や`queryFunction`（第 2 引数に渡す fetch する関数）がパラメータによって一意に決まる場合は以下のような書き方になります。

```js
useQuery(['todos', { status: 'done' }], ...]
// queryKey === ['todos', { status: 'done' }

const todoId = 5
useQuery(['todos', todoId], ...]
// queryKey === ['todos', 5]

useQuery(['todos', todoId, { preview: true }], ...]
// queryKey === ['todos, 5, { preview: 'true' } ]
```

このとき`queryFunction`の引数は`useQuery`の第一引数と対応しています。

```js
// 例の3つ目
// useQuery(['todos', todoId, { preview: true }], ...] // queryKey === ['todos, 5, { preview: 'true' } ]
const fetchTodoList = (key, todoId, { preview }) => {
 ...
}
```

## queryKey の並び順による影響

`queryKey`の並び順は

- object 内は考慮されない
- array 内は考慮される

です。

```js
// 以下は全て同じ扱い
useQuery(['todos', { status, page }], ...)
useQuery(['todos', { page, status }], ...)
useQuery(['todos', { page, status, other: undefined }], ...)

// 以下は全て異なる扱い
useQuery(['todos', status, page], ...)
useQuery(['todos', page, status], ...)
useQuery(['todos', undefined, page, status], ...)
```

## 複数クエリがある場合の挙動

同一ページで複数のクエリを叩くパターンとして、

- 並列クエリ...複数のクエリを同時に叩く
- 直列クエリ...あるクエリの結果を元にクエリを叩く

があります。

並列クエリの場合にはクエリを複数の書くだけで簡単に実装できます。
後者の場合はこれまではメソッドチェーンやコールバック、`Suspence`を駆使して実装していたでしょう。
しかし、react-query ではとても簡潔に書くことができます。

ここでは、以下の例をについて見ていきましょう。

**_`email`からユーザ情報を取得し、取得したユーザの`id`からユーザに紐づく`projects`一覧を取得する_**

従来だとこのような感じでしょうか

```js
cosnt fetchProjects = async () => {
  const user = await getUserByEmail(email)
  const projects = await getProjectsByUser(user.id)
  setProjects(projects)
}

useEffect(() => {
  fetchProjects()
}, [])
```

### パターン 1: queryKey に falsy な値を渡す

1 つ目のパターンは`queryKey`に `falsy` な値を渡すパターンです。

```js
// react-queryを用いる際はdataを別の変数名で定義します。
const { data: user } = useQuery(["user", { email }], getUserByEmail);

const { data: projects } = useQuery(
  // userがnullでなくなったらqueryFunctionが実行されます
  user && ["projects", { userId: user.id }],
  getProjectsByUser
);
```

とても簡単で見やすいですね！

### パターン 2: queryKey の Item に falsy な値を渡す

パターン 1 では`queryKey`自体が`falsy`でしたが、`queryItem`が`falsy`な場合でも同様の効果が得られます。
これによって、複数の依存があるクエリの場合でもネストが無いためとても読みやすいですね！

```js
const { data: user } = useQuery(["user", { email }]);

const { data: projects } = useQuery(
  ["projects", user && user.id],
  getProjectsByUser
);
```

また、コンパイラが対応している場合には[Optional Chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)で書くことも可能です！

```js
const { data: projects } = useQuery(["projects", user?.id], getProjectsByUser);
```

### パターン3: queryFunctionでの例外検知

```js
const { data: user } = useQuery(['user', { email }])

const { data: projects } = useQuery(
  () => ['projects', { userId: user.id }]
)
```

``queryKey``に``Function``を渡すこともできます。
上記のコードでは、`user`が`null`のときに`user.id`へアクセスしようとすると例外が発生します。
このような場合、例外が発生しなくなるまで再取得を試みてくれます。
一番`Suspence`を使うときに近いですね。

## API

### useQuery

準備中...

### usePaginatedQuery

準備中...

### useInfiniteQuery

準備中...

### useMutation

準備中...

### queryCache

準備中...

### queryCache.prefetchQuery

準備中...

### queryCache.getQueryData

準備中...

### queryCache.setQueryData

準備中...

### queryCache.refetchQueries

準備中...

### queryCache.removeQueries

準備中...

### queryCache.getQuery

準備中...

### queryCache.subscribe

準備中...

### queryCache.clear

準備中...

### useIsFetching

準備中...

### ReactQueryConfigProvider

準備中...

### setConsole

準備中...

## おわりに

サーバが`REST API`で実装されている場合、これを使うことですごく書きやすくなりそうな雰囲気を感じます。
`GraphQL`にも対応はしていますが、`Apollo Client`の方が純粋に特化しているのでそちらを使ったほうが良さそうですね。

コード部分も含め全部書き写しているので、間違いやタイポがあったら感想でご指摘、修正依頼等お願いします。