67
56

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-01

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 = 1todoを取得する例ですが、API を叩く関数では promise を返し、使う側はkeytodoIdqueryKeyとして配列で渡すだけで、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で利用するのは厳しいようです。

yarn add @types/react-query

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

queryKey とは

queryKeyはこのモジュールの最重要ポイントです。string もしくはstring, numberの値とそれらを含むobjectarrayで示されます。
キャッシュの管理を行うために必須な値であり、雰囲気としてはGraphQLqueryに似ています。
このモジュールの強みは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 つ目のパターンはqueryKeyfalsy な値を渡すパターンです。

// 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でしたが、queryItemfalsyな場合でも同様の効果が得られます。
これによって、複数の依存があるクエリの場合でもネストが無いためとても読みやすいですね!

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 }]
)

queryKeyFunctionを渡すこともできます。
上記のコードでは、usernullのときに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の方が純粋に特化しているのでそちらを使ったほうが良さそうですね。

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

67
56
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
67
56

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?