こんにちは、はるです.
所属している「もりけん塾」でReact課題に取り組んでいます.
問題文やレビュー内容は非公開なので、調べたことをまとめることにしました.
初心者の学習ノートなので、認識の間違えている点がありましたらお問い合わせからご指摘いただけるとうれしいです.
Tanstack Query
ついにもりけんさん(@terrace_tec)に見ていただいていたReact課題も最終課題 :O
最近は、Reactを使ってデータをfetchしたり、エラーハンドリングやローディングを実装することに時間を割いていました。
レビューでもりけんさんからTanstackQueryの存在を教えていただき、調べながら実装をブラッシュアップしていきました。
TanstackQueryとは?
TanstackQuery(旧React Query)は、Reactアプリケーションにおけるデータの取得、キャッシュ、同期、更新を簡単に管理するためのライブラリです。
公式サイトのドキュメントはこちらです。
メリット
TanstackQueryの主なメリット✏️
- データをfetchするやりとりを簡潔に記述できる。
- デフォルトでキャッシュ機能が備わっていて、効率的なデータ管理ができる
- データの自動リフレッシュ機能により、最新のデータを常に取得できる。
- エラーハンドリングのコードもシンプル!
- 複数のリクエストを一つにまとめて処理することで、API呼び出しの数を減らせる ←ドキュメントに書いてあったけど今回は使っていない。
基本の使い方
まず、TanstackQueryをインストールします。
npm install @tanstack/react-query
アプリケーションのエントリーポイントでQueryClient
とQueryClientProvider
を設定します。
import React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import App from './App';
const queryClient = new QueryClient();
const Root = () => (
<React.Strictmode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>
);
export default Root;
- QueryClientでReact Queryの設定やキャッシュを管理するインスタンスを作成します。
- QueryClientProviderコンポーネントで、インスタンスを渡すことで、配下のコンポーネント全体で使用することができます。
useQueryフック
データフェッチにはuseQuery
フックを使用します。
例えば下記のように書いてみました。
import React from 'react';
import { useQuery } from '@tanstack/react-query';
const fetchUsers = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
};
const Users = () => {
const { data, error, isLoading } = useQuery(['users'], fetchUsers);
if (isLoading) return <div>Loading...</div>;
if (isError) return <p>Error: {error.message}</p>;
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
useQuery
フックは、主に以下の2つのパラメータを取ります。
useQuery([queryKey], queryFn)
queryKey
:クエリの一意の識別子。キャッシュや再フェッチ時に使用されます。この例では['users']
がクエリキーとして使用されています。queryFn
:データを取得するための非同期関数→この例ではfetchUsers()
useQuery
フックは、以下のプロパティを持つオブジェクトを返します。
data
:取得されたデータ。データフェッチが成功した場合にこのプロパティにアクセスできます。error
:エラーが発生した場合にそのエラーオブジェクトが格納されます。isLoading
:データがフェッチされている間、このプロパティはtrue
になります。
queryKeyを一意のものにする理由
queryKeyが一意である必要がある理由は、queryFnの関数の返り値が、queryKeyの値に紐付けてキャッシュされるからです。
2回目のfetchからはqueryFnは実行されず、キャッシュのデータを返すため、queryKeyが被っているuseQueryがあると、誤ったキャッシュデータが返却されてしまうことになります。
なぜ配列なのか?についても調べましたが、 配列にすることで複数の値をキーにすることができる(より複雑なものも設定できる)と解釈しました。
例) ユーザーIDとフェッチするデータの種類など
大きなプロジェクトになってくると、queryKeyを手動で設定すると事故が起きやすくなるというのも知りました。今回は取り入れられていませんが、構造のルールを決めたり、クエリキーを生成する関数を備えたオブジェクト(カスタムフックで使用できる)を使用するなどの対策も調べました。
深められていませんが、公式に「queryKeyFactory」というクエリ・キー管理機能が紹介されていました。
ふむふむ、と思っただけです.. 😶
エラーハンドリング
エラーハンドリングはisError
プロパティをチェックすることで行います。この例では、エラーが発生した場合にエラーメッセージを表示させました。
if (isError) return <p>Error: {error.message}</p>;
ローディング状態のハンドリング
データがフェッチされている間はisLoading
プロパティをチェックして、ローディング状態を表示します。
if (isLoading) return <div>Loading...</div>;
SuspenseとErrorBoundary
React.SuspenseとErrorBoundaryを使うことで、先ほどのisLoadingとisErrorをカットしてよりシンプルに書くことができます!
Suspense
Suspenseを使うとデータフェッチ中にローディングUIを表示させることができます。今回は useSuspenseQueryを使用します。フックの使い方はuseQueryと一緒なので置き換えます。
import { useSuspenseQuery } from '@tanstack/react-query';
const Users = () => {
const { data } = useSuspenseQuery(['users'], fetchUsers);
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
非同期データのフェッチが発生するコンポーネントをSuspenseで囲みます。
Suspenseにfallbackを設定することでローディングを実装できます。
const App = () => (
<Suspense fallback={<div>Loading...</div>}>
<Users />
</Suspense>
);
- useSuspenseQueryはv5から新しく追加されたフックで、suspenseモードをデフォルトで有効にしてくれるuseQueryの代替手段です。
(useQueryを使うときは別途Suspenseモードをtrueに切り替える必要がある。) - useQueryと挙動は一緒ですが、enabledやplaceholderDataオプションは使用できない特徴があるので、”条件付きでQueryを有効/無効にする必要がある”場合などは使い分けが必要です。
ErrorBoundary
エラーハンドリングはErrorBoundaryを使うとよりスッキリ書くことができます。
ErrorBoudaryを使うには専用のクラスを書く必要があるのですが、公式Docを見てみると「react-error-boundary」を使いましょう!自分でエラーバウンダリクラスを書く必要はありません!とのこと。
パッケージをインストールして使います。
npm install react-error-boundary
import
import { ErrorBoundary } from 'react-error-boundary';
エラーを表示させるフォールバックを定義
const ErrorFallback = ({ error, resetErrorBoundary }) => {
return (
<div role="alert">
<p>Something went wrong:{error.message}</p>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
};
resetErrorBoudaryは課題では使わなかったのですが、もりけんさんにお聞きしたので調べてみました👀
- 状態のリセット→
resetErrorBoundary
を呼び出すと、エラーバウンダリーのエラー状態がリセットされ、エラーが発生する前の状態に戻る - 再レンダリングのトリガー→リセット後、関連するコンポーネントが再レンダリングされ、通常のレンダリングサイクルが再開される
= ユーザーがエラーをリセットして再試行できるようにするため。 再試行ボタンとか(?
Suspenseと同じようにフェッチが発生するコンポーネントをErrorBoudaryで囲む!
先ほどのFallbackをこちらで指定します。
const App = () => (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Suspense fallback={<div>Loading...</div>}>
<Users />
</Suspense>
</ErrorBoundary>
);
(番外編)ErrorBoudaryの専用クラス … ☁️
公式を読んでみると、クラスの書き方も載っていました。
内容はエラーに反応して state を更新し、ユーザにエラーメッセージを表示するための static getDerivedStateFromError を提供するものです。通常これはエラーレポートを何らかの分析サービスに送信できるようにするための componentDidCatch と一緒に使用されます。
まとめ
まだまだ触りですがいつもtry,catchで書いていたデータのフェッチをTanstack Queryを使うことでシンプルに書くことができました:)
Error BoudaryやSuspenseについてももりけんさん(@terrace_tec)にアドバイスをいただき、触れることができました!
全14課題、丁寧なレビューで大変お世話になりました!ありがとうございました!
—-
💡もりけん塾で勉強しています .. JavaScriptを一緒に勉強する塾生さん募集中です💡
もりけんさん(@terrace_tec)のHPはこちら