React-Queryとは?使い方と重要性を分かりやすく解説
React-Queryを開始する前の前提条件ございます。
・ React フックと関数コンポーネントに関する中級レベルのスキルが不可欠です。
・ JavaScriptでのREST APIとデータ取得の基本的な理解。
・マシンにNode.jsがインストールされていることを確認してください。
React-Queryとは?
React-Queryは、ReactおよびNext.jsコードベース用に構築されたライブラリであり、ネットワークリクエストを作成する際に従うべきすべてのベストプラクティスが組み込まれています。
ReactおよびNext.jsアプリでのサーバー状態の取得、キャッシュ、同期、および更新を簡単にする React用のデータ取得ライブラリという事です。
これは、キャッシュをフェッチしてサーバーからデータを同期する方法を簡素化するReact ライブラリであり、Reactの不足しているデータフェッチライブラリとしてよく説明されますが、より技術的な用語で言えば、React アプリケーションで上記のすべてのプロセスを簡単に実行できます。
React自体には、サーバーからデータをフェッチする方法に関する意見はありません。
最も基本的なアプローチは、コンポーネントが最初に useEffectにマウントされたときにブラウザのフェッチAPIを使用し、次にuseStateを使用して応答を管理することです。
このプロセスは機能しますが、キャッシュ、再試行、重複排除などの要件がある場合です。
データ取得コードを簡素化するだけでなく、これらの複雑な要件をすぐに処理できます。
つまり、React-Queryを使用すると、グローバルな状態を変更することなく、シンプルかつ宣言的な方法でReactベースのアプリケーションのデータをフェッチ、キャッシュ、および更新できます。
React-Queryは、Reactのエコシステムで最も人気のあるライブラリの1つになりつつあります。
ReduxやuseEffectとの違いは?
React-Queryは「Axios」や「fetch API」と同じではなく、「Redux」または「Context API」の代替品でもないことを明確にする必要があります。
React-Queryは主にデータ同期に使用され、「Redux」はグローバルな状態管理に使用されます。
useEffectなどの一般的なデータ取得パターンとの違いは、React-Queryは前回取得したデータを返し、その後もう一度取得し直すという点です。
もしリソースが最初と同じであれば、React-Queryはページの再読み込みを強制することなく、両方のデータを参照として保持致します。
React-Queryを使用する理由
これはデータを取得するだけなのか?私たち開発者は、JS FetchAPI
またはAxios
とuseEffect
およびuseState
フックを使用してそれらを行ってきました。
しかし、突然useEffectと useStateの代わりに React-Query
を使用するのはなぜなのか?
ご存じかもしれませんが、ほとんどの従来の管理ライブラリはクライアント状態の操作には優れていますが、非同期またはサーバー状態の操作では煩わしくなりました。
React-Queryを使用する主な理由の1つは、頻繁な更新、キャッシュ、およびサーバーとの同期が必要な非同期データを扱う場合、React-Queryが非常に便利であることです。
ただし、React-Queryがこのような機能を提供するためには、その概念や使い方について理解する必要があります。
そのため、React-Queryを使用する前に、そのドキュメントやチュートリアルをしっかりと学習し、適切に使いこなすことが重要です。
前述した通り、React-Queryは、グローバルな状態がなくてもデータの取得、キャッシュ、更新を簡素化できるため、使いやすく、必要に応じてカスタマイズできます。
これにより、従来の方法よりも効率的に非同期データを処理できるようになります。
React-Queryを使用したデータ取得
CRAで新しいReact JSプロジェクトを作成し、react-query
をインストールします。
npm i react-query // or yarn add react-query
次に、App.js
ファイルでreact-queryからuseQuery
をインポートします。
// App.js import { useQuery } from "react-query";
単純なfetchData
関数を記述してRest APIからデータをフェッチします。
本日はfetch()
を使用していきますが、Axiosやその他の方法でも可能です。
JSONプレースホルダーAPI にネットワークリクエストを行うasync関数を定義する必要があります。
// App.js const fetchData = async () => { const res = await fetch("https://jsonplaceholder.typicode.com/users"); return res.json(); };
そしてuseQueryフックを使用して、下記のようにデータ取得を管理できます。
const response = useQuery("users", fetchData);
useQueryフックには2つの引数が必要となります。
1つはこのクエリのkey
です。
そのために、文字列のusers
を使用しています。
最初の引数として配列を置くこともでき、配列が渡された場合では各アイテムは安定したクエリキーにシリアル化されます。
2つ目は、データをフェッチする関数となります。
先述で作成したfetchDataのasync関数を渡します。
また、さまざまなオプションの3番目の引数としてオブジェクトを渡すことが可能です。
useQueryから返される応答は非常に重要となっています。
下記のプロパティがあります。
・ data
・ error
・ failureCount
・ isError
・ isFetchedAfterMount
・ isFetching
・ isIdle
・ isLoading
・ isPreviousData
・ isStale
・ isSuccess
・ refetch
・ remove
・ status
data
は取得した実際のデータです。
status
は、応答に応じてloading、error、success、またはidleになります。
上記これらすべてのプロパティにはさまざまな用途があります。
本日は、data
とstatus
、 error
およびisError
プロパティを使用する例を示します。
const { data, status } = useQuery("users", fetchData);
前述したように、useQueryフックは2つの引数を取り、最初の引数はキャッシュする文字列であり、クエリ結果を追跡します。
2番目の引数は、HTTPリクエストを作成するために定義するfetchData
関数です。
React-Queryは、キャッシュとデータの更新を内部的に処理します。
これで、データを使用してブラウザに表示する事ができます。
// App.js import { useQuery } from "react-query"; const fetchData = async () => { const res = await fetch("https://jsonplaceholder.typicode.com/users"); return res.json(); }; const App = () => { const { data, status } = useQuery("users", fetchData); return ( <div className="App"> {status === "error" && <p>Error fetching data</p>} {status === "loading" && <p>Fetching data...</p>} {status === "success" && ( <div> {data.map((user) => ( <p key={user.id}>{user.name}</p> ))} </div> )} </div> ); }; export default App;
上記で行った事は、status
を確認してデータを表示することです。
これは、React-QueryのuseQueryフックの使用方法を簡単に説明したものとなります。
QueryClientProvider
基本的には、React.jsアプリケーションでReact-Queryを構成するには、データ取得を必要とする最上位のコンポーネントをQueryClientProvider
コンポーネントでラップする必要があります。
QueryClientは、他のコンポーネントがキャッシュを利用できるようにします。
React-Queryを起動するために、ルートディレクトリのファイルindex.js
に下記のような基本的な設定をします。
// index.js v18〜 import React from 'react'; import { createRoot } from 'react-dom/client'; import 'index.css'; import App from './App'; import { QueryClient, QueryClientProvider } from "react-query"; const root = ReactDOM.createRoot(document.getElementById('root')); const queryClient = new QueryClient(); root.render( <React.ScriptMode> //ラップ <QueryClientProvider client={queryClient}> <App /> </QueryClientProvider> </React.ScriptMode> );
// index.js import React from 'react' import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import { QueryClient, QueryClientProvider } from "react-query"; const queryClient = new QueryClient(); ReactDOM.render( //ラップ <QueryClientProvider client={queryClient}> <App /> </QueryClientProvider>, document.getElementById('root') );
QueryClientProvider
の子コンポーネントは、React-Queryライブラリが提供するフックにアクセスできるようになり、QueryClient
インスタンスが提供されるようになりました。
このインスタンスを使用し、React-Queryライブラリが提供するフックにアクセスすることになります。
クエリクライアントをインスタンス化し、コンポーネントツリーに提供すると、すべての子コンポーネントがプロのようにデータをフェッチできるようになります。
これは開発中のデバッグに使用される事も多いです。
import {useQuery} from 'react-query'; const fetchData = async () => { const res = await fetch("https://jsonplaceholder.typicode.com/users"); return res.json(); }; function Users(){ const {data, status} = useQuery('users', fetchData) if(status === "Loading"){ return <div>Loading...</div> } if(status === "isError"){ return <div>Loading…</div> } return( <div className='container'> <h1>Users</h1> { data.map((user, index) => { return <li key={index}>{user.title}</li> }) } </div> ) } export default Users;
コンポーネントの return
キーワードの前に、基本的なロジックを実行し、アプリケーションがロード状態にあるかどうか、またはエラーが発生したかどうかを確認します。
useQueryから返される応答にerror
とisError
プロパティを使用するのが基本です。
const {data, status, error, isError} = useQuery('users', fetchData) ; if(status === "Loading"){ return <div>Loading...</div> } if(isError){ return <div>Error! {error. message}</div> }
React-Queryを使用すると、エラー処理も簡単になります。
上記の通り、React-Queryはサーバーの状態を管理するための最良のライブラリの1つです。
アプリデータがユーザーを制御し始める前に制御することが可能となります。
クエリを並列で実行したい場合は、useQueryを並べて書けば問題ありません。
ただし、配列に基づいてクエリしたい場合や、Suspenseを使っている場合はuseQueriesを使う必要がありますので注意して下さい。
const userQueries = useQueries( users.map((user) => { return { queryKey: ['user', user.id], queryFn: () => fetchUserById(user.id), }; }), )``;
もう少し、詳しく学ばれたい方は下記へ進んで下さい。
クエリ
クエリするには下記の2つが必要となります。
・ ユニークな独自のKey
・ Promise(データをresolveするか、またはエラーを投げる)
const result = useQuery('users', fetchData); // or const result = useQuery({ queryKey: 'users', queryFn: fetchData, });
・ isLoading or status === 'loading'
初期ロード中
・ isError or status === 'error'
エラーが発生した状態
・ error
エラー内容
・ isSuccess or status === 'success'
データ取得が成功した状態
・ isIdle or status === 'idle'
クエリが無効された状態
・ isFetching or isLoading
なんらかの通信中である
クエリキー
・ クエリキーに基づいてキャッシュが行われる。
・ シリアライズ可能な値ならなんでもKeyとして使用できる。
useQuery('users', ...) // queryKey === ['users'] useQuery(['user', 5], ...) // queryKey === ['user', 5] useQuery(['user', 5, { preview: true }], ...) // queryKey === ['user', 5, { preview: true }]
クエリが特定のid等に基づいて行われるものであるならば、その値をキーとして含めておくことが重要です。
function Users({ userId }) { const result = useQuery(['users', userId], () => fetchDataById(userId)); }
クエリを実行する関数
データ取得に失敗した時は必ずエラーを投げることです。
axios
とは異なり、fetch
はデフォルトではエラーを投げないので注意する必要があります。
import { useQuery } from 'react-query'; // React QueryライブラリのuseQueryフックを使用して、APIからユーザーデータを取得する関数を定義する useQuery(['users', userId], async () => { // APIエンドポイントにアクセスし、レスポンスを取得するために、fetch関数を使用する const response = await fetch('/users/' + userId); // fetch関数はデフォルトでエラーを投げないため、レスポンスがエラーである場合は、エラーを投げる必要がある // レスポンスがエラーであるかどうかは、okプロパティを確認することで判断できる if (!response.ok) { // レスポンスがエラーである場合は、エラーを投げる throw new Error('ネットワークのレスポンスが良くありません。'); } // レスポンスが正常である場合は、JSONデータを解析して返す return response.json(); });
上記のコード例は、React-QueryライブラリのuseQueryフックを使用して、APIからユーザーデータを取得する関数を定義しています。
関数内では、「fetch」関数を使用してAPIエンドポイントにアクセスし、レスポンスを取得します。
「fetch」関数はPromiseを返すため、レスポンスが帰ってくるまで待機する必要があります。
その後、レスポンスの「okプロパティ」を確認して、レスポンスがエラーでないことを確認します。
レスポンスがエラーの場合は、エラーを投げて関数を中断します。
レスポンスが正常な場合は、JSONデータを解析して返します。
エラーを投げることで、プログラムが問題を認識し、適切に対処できるようになります。
また、React-Queryライブラリによってキャッシュされたデータが返される可能性があるため、常にAPIからデータを取得するために、関数を渡す必要があります。
バックグラウドでのデータ取得をユーザーに通知
・ isFetching
ではあらゆる通信でtrue
となります。
・ idLoading
は初回データ取得の時だけtrue
となります。
・ アプリ全体での通信状態を取得したい場合ではuseIsFetching
を使用して下さい。
以下のコードに、Reactアプリ内でデータを取得している場合に、取得中かどうかを示すフラグである「isFetching」と、初回データ取得時かどうかを示すフラグ「isLoading」を使用する方法を示します。
import React, { useState, useEffect } from "react"; function App() { // データを管理するstate const [data, setData] = useState([]); // データ取得中かどうかを管理するstate const [isFetching, setIsFetching] = useState(false); // 初回のデータ取得中かどうかを管理するstate const [isLoading, setIsLoading] = useState(true); useEffect(() => { const fetchData = async () => { // データ取得中であることを示すフラグを設定する setIsFetching(true); // データを取得する const response = await fetch("https://example.com/data"); const data = await response.json(); // データを更新する setData(data); // データ取得が完了したことを示すフラグを設定する setIsFetching(false); setIsLoading(false); }; // 初回のアプリのレンダリング時にデータを取得する fetchData(); }, []); return ( <div> {isLoading ? ( // 初回データ取得中であればLoading...を表示する <p>Loading...</p> ) : ( // データをUIに表示する <ul> {data.map((item) => ( <li key={item.id}>{item.name}</li> ))} </ul> )} </div> ); }
上記の例では、useEffectフックを使用して、初回のアプリのレンダリング時にデータを取得しています。
このとき、「isLoading」フラグをtrue
に設定して、初回のデータ取得が行われたことを示します。
データの取得中には、「isFetching」フラグをtrue
に設定し、データの取得が完了したら「false」に戻します。
これにより、UIが取得中であることをユーザーに通知することができます。
最後に、「isLoading」フラグをfalse
に設定することで、初回のデータ取得が完了したことを示し、データをUIに表示することができます。
また、アプリ全体での通信状態を知りたい場合には、react-queryライブラリの「useIsFetching」フックを使用することで、現在アクティブなAPIリクエストの数を取得することができます。
以下のようにimportして使用することができます。
import { useIsFetching } from "react-query";
Mutations
データの作成・更新・削除する場合はuseQueryではなくuseMutation
フックを使用します。
// useMutationフックを使用して、データを作成・更新・削除する方法 // `useMutation`フックは、axiosなどのHTTPクライアントを使用してAPIを呼び出すことができます。 // useMutationフックをインポートします import { useMutation } from 'react-query'; // useMutationフックを使用して、API呼び出しを実行します const { mutate, isLoading, isError, isSuccess, error, data } = useMutation( (newUser) => axios.post('/users', newUser) // データを作成する場合はPOSTリクエストを使用します // データを更新する場合はPUTリクエストを使用します // データを削除する場合はDELETEリクエストを使用します ); // mutate関数を使用して、APIを呼び出します mutate({ id: 1234, title: 'test' }); // ここで、実際のデータを渡します
上記のコードは、以下のことを行っています。
useMutationフックを使用して、API呼び出しを実行するための準備をします。
そしてmutate関数を使用して、APIを呼び出します、データを渡すことで、APIに必要な情報を提供します。
API呼び出しの結果を、「isLoading」、「isError」、「isSuccess」、「error」、「data」などの変数で受け取ります。
「isLoading」、「isError」、「isSuccess」、「error」、「data」の各変数は、以下のような役割を持っています。
・ isLoading: API呼び出しが進行中であるかどうかを示すブール値です。
・ isError: API呼び出し中にエラーが発生したかどうかを示すブール値です。
・ isSuccess: API呼び出しが成功したかどうかを示すブール値です。
・ error: API呼び出し中に発生したエラーに関する情報を含むオブジェクトです。
・ data: API呼び出しの結果として取得したデータを含むオブジェクトです。
クエリの無効化と停止
enabled
オプションをfalse
設定することで以下のようになります。
・ キャッシュデータが存在する場合は、isSuccess
状態になり、データが提供されます。
・ キャッシュデータがない場合はisIdle
状態になり、マウント時にはクエリが実行されません。
・ バックグラウンドでは再クエリされません。
・ invalidateQueries
およびrefetchQueries
が発火された場合でも、再クエリしません。
・ refetch
を使って手動でクエリを実行することは可能です。
enabledオプションは、クエリの実行結果を使って別のクエリを実行する際に使用します。
下記の例では、「userId」が存在する場合にのみこのクエリが実行されるように設定されています。
const { isIdle, data: projects } = useQuery( ['projects', userId], getProjectsByUser, { // userIDが存在する場合のみこのクエリを実行する enabled: !!userId, }, );
「enabled」オプションがfalse
に設定されている場合、クエリは手動で再実行する必要があります。
invalidateQueries
およびrefetchQueries
を使用しても、クエリは再実行されません。
また、「enabled」オプションをtrue
に設定すると、クエリが自動的に再実行されます。
最後に
React-Queryは、Webアプリケーションでデータを管理するためのライブラリです。
例えば、APIからデータを取得する際に、データの取得状態を管理し、必要に応じて再取得することができます。
これまで、Reactアプリケーションでデータ管理を行うために「Redux」や「Context API」が使われてきましたが、React-Queryを使うことで、より簡単にデータ管理ができるようになりました。
React-Queryを使うことで、APIからデータを取得する処理をカスタムフックに分離することができます。
これにより、コンポーネント内でデータ取得に関するロジックを記述する必要がなくなり、コードの再利用性や保守性が向上します。
また、React-Queryは、データの取得状態やエラーの管理、キャッシュ機能などを備えているため、データの取得に関するロジックを自分で実装する必要がありません。
そのため、コードの量が削減され、開発効率が向上します。
React-Queryは、Reactアプリケーションでデータ管理を行うために、非常に便利なライブラリです。
初学者でも比較的簡単に学ぶことができるため、今後のReactアプリケーション開発において、活用することをお勧めします。
本日は以上となります。
最後まで読んで頂きありがとうございます。