Skip to content
⚠️ This article was written in 2020. Some content may be outdated.

React Query:フロントエンドのデータ取得とキャッシュを再考する

React Query(現在は TanStack Query に名称変更)が解決する核心的な問題は、サーバー状態の管理です。Redux や MobX はクライアント状態の管理に優れていますが、「サーバーからデータを取得、キャッシュ、同期する」といった要件に対して、それらが提供するツールは低レベルすぎます。React Query はこの課題を、すぐに使えるソリューションとして実現しました。

インストールと基本的な使い方

bash
npm install react-query
jsx
import { QueryClient, QueryClientProvider, useQuery } from "react-query";

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <UserList />
    </QueryClientProvider>
  );
}

function UserList() {
  const { data, isLoading, error } = useQuery(
    "users", // 一意のクエリキー
    () => fetch("/api/users").then((r) => r.json()),
  );

  if (isLoading) return <div>加载中...</div>;
  if (error) return <div>出错了:{error.message}</div>;

  return (
    <ul>
      {data.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

なぜ useEffect + useState を使わないのか

従来の書き方では、毎回ローディング、エラー、データの3つの状態を手動で記述する必要がありました:

jsx
// ❌ 従来の書き方、ボイラープレートコードが多い
function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch("/api/users")
      .then((r) => r.json())
      .then((data) => {
        setUsers(data);
        setLoading(false);
      })
      .catch((err) => {
        setError(err);
        setLoading(false);
      });
  }, []);
  // ...
}

React Query が提供するのは、よりシンプルな書き方だけではありません:

  • 自動キャッシュ:同じキーのデータは1回のみリクエスト
  • バックグラウンドリフレッシュ:ウィンドウが再フォーカスされると自動的に再リクエスト
  • 重複排除:複数のコンポーネントが同じデータをリクエストする場合、1回のみリクエスト
  • 自動 GC:未使用のキャッシュは自動的に削除

キャッシュと staleTime

jsx
// staleTime: データをどれだけ「新鮮」とみなすか(再リクエストしない)
const { data } = useQuery("users", fetchUsers, {
  staleTime: 5 * 60 * 1000, // 5分間は再リクエストしない
  cacheTime: 10 * 60 * 1000, // キャッシュを10分間保持(購読者がいなくても)
});

データ変更:useMutation

jsx
import { useMutation, useQueryClient } from "react-query";

function CreateUser() {
  const queryClient = useQueryClient();

  const mutation = useMutation(
    (newUser) =>
      fetch("/api/users", {
        method: "POST",
        body: JSON.stringify(newUser),
      }).then((r) => r.json()),
    {
      // 作成成功後、'users' キャッシュを無効にして再リクエストをトリガー
      onSuccess: () => {
        queryClient.invalidateQueries("users");
      },
    },
  );

  return (
    <button
      onClick={() => mutation.mutate({ name: "Alice" })}
      disabled={mutation.isLoading}
    >
      {mutation.isLoading ? "创建中..." : "创建用户"}
    </button>
  );
}

ページネーションクエリ

jsx
const [page, setPage] = useState(1);

const { data, isPreviousData } = useQuery(
  ["users", page],
  () => fetchUsers(page),
  {
    keepPreviousData: true, // ページ移動時に前のページデータを保持し、ローディングのちらつきを防止
  },
);

まとめ

React Query の核となる考え方は、クライアント状態とサーバー状態を区別し、サーバー状態に特化したツールを提供することです。導入後は、ほとんどの Redux store 内の非同期 action を直接削除でき、コード量は通常 30〜40% 削減されます。2020 年の React プロジェクトには非常に導入する価値があります。

MIT Licensed