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
を簡単に取得できます。
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で利用するのは厳しいようです。
For now, no. I'm sorry I can't give you any TS hugs today.
— Tanner Linsley (@tannerlinsley) February 26, 2020
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.
I'll change some day. But not today.
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
とするときはこのように書きます。
useQuery('todos', ...)
// queryKey === ['todos']
右側のコメントがモジュール内で利用されるqueryKey
で、配列型に変換されるようになっています。
Array Keys
もし、その他の情報やqueryFunction
(第 2 引数に渡す fetch する関数)がパラメータによって一意に決まる場合は以下のような書き方になります。
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
の第一引数と対応しています。
// 例の3つ目
// useQuery(['todos', todoId, { preview: true }], ...] // queryKey === ['todos, 5, { preview: 'true' } ]
const fetchTodoList = (key, todoId, { preview }) => {
...
}
queryKey の並び順による影響
queryKey
の並び順は
- object 内は考慮されない
- array 内は考慮される
です。
// 以下は全て同じ扱い
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
一覧を取得する
従来だとこのような感じでしょうか
cosnt fetchProjects = async () => {
const user = await getUserByEmail(email)
const projects = await getProjectsByUser(user.id)
setProjects(projects)
}
useEffect(() => {
fetchProjects()
}, [])
パターン 1: queryKey に falsy な値を渡す
1 つ目のパターンはqueryKey
に falsy
な値を渡すパターンです。
// 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
な場合でも同様の効果が得られます。
これによって、複数の依存があるクエリの場合でもネストが無いためとても読みやすいですね!
const { data: user } = useQuery(["user", { email }]);
const { data: projects } = useQuery(
["projects", user && user.id],
getProjectsByUser
);
また、コンパイラが対応している場合にはOptional Chainingで書くことも可能です!
const { data: projects } = useQuery(["projects", user?.id], getProjectsByUser);
パターン3: queryFunctionでの例外検知
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
の方が純粋に特化しているのでそちらを使ったほうが良さそうですね。
コード部分も含め全部書き写しているので、間違いやタイポがあったら感想でご指摘、修正依頼等お願いします。