deve.K

エンジニアが未来を切り開く。

React-Queryとは? 使い方と重要性

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またはAxiosuseEffectおよびuseStateフックを使用してそれらを行ってきました。

しかし、突然useEffectと useStateの代わりに 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になります。

上記これらすべてのプロパティにはさまざまな用途があります。

本日は、datastatuserrorおよび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から返される応答にerrorisErrorプロパティを使用するのが基本です。

 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はデフォルトではエラーを投げないので注意する必要があります。

useQuery(['users', userId], async () => {
  const { ok, json } = await fetch('/users/' + userId);
  if (!ok) {
    throw new Error('ネットワークのレスポンスが良くありません。');
  }
  return json();
});

バックグラウドでのデータ取得をユーザーに通知

isFetchingではあらゆる通信でtrueとなります。

idLoadingは初回データ取得の時だけtrueとなります。

アプリ全体での通信状態を取得したい場合ではuseIsFetchingを使用して下さい。

Mutations

データの作成・更新・削除する場合はuseQueryではなくuseMutationを使用します。

const {
  status,
  isLoading,
  isError,
  isSuccess,
  mutate,
  error,
  data,
} = useMutation((newUser) => axios.post('/users', newUser));

mutate({ id: 1234, title: 'test' });

クエリの無効化と停止

enabledオプションをfalse設定することで下記のようになります。

キャッシュデータが存在する場合は、isSuccess状態になり、dataが提供されます。

キャッシュデータがない場合はisIdle状態になり、マウント時にはクエリが実行されません。

バックグラウンドでは再クエリされません。

invalidateQueriesおよびrefetchQueriesが発火されても再クエリしません。

refetchを使って手動でクエリを実行が可能です。

enabledオプションは、クエリの実行結果を使って別のクエリを実行する際に使用します。

const { isIdle, data: projects } = useQuery(
  ['projects', userId],
  getProjectsByUser,
  {
   // userIDが存在する場合のみこのクエリを実行する
    enabled: !!userId,
  },
);

React-Queryライブラリはまた別途記事に致します。

最後に

ほとんどの場合、今まではReact JSでのプロジェクトの状態管理にはReduxContext APIを使用してきました。

React-Queryライブラリは、Webサービスのリクエストに関連するデータを管理するのに役立ち、ユーザーエクスペリエンスを向上させながら、複雑さが増してもアプリケーションを保守しやすくします。

コードベースを維持しやすくするために、コンポーネント内で直接React-Queryを使用するのではなく、カスタムフックを作成してクエリを処理することをお勧めします。

React-Queryのようなライブラリを使用すると、データ取得のためにカスタムライブラリを維持する必要がなくなります。

現在では、React-Queryがより強力になり、コミュニティの間で人気が高まっていると思います。

これは、すべてのReact開発者にお勧め致します。

本日は以上となります。

最後まで読んで頂きありがとうございます。

プライバシーポリシー