7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React QueryでフロントエンドのPerformanceを改善する実践ガイド

7
Posted at

React QueryでフロントエンドのPerformanceを改善する実践ガイド

対象読者

  • React Queryの基本を理解している方
  • フロントエンドのパフォーマンス改善に興味がある方

所要時間: 約12分


はじめに

React Queryを「データ取得ライブラリ」として使っている方は多いと思います。

しかし、適切に設定するだけでフロントエンドのパフォーマンスは大きく向上します。

本記事では、「なんとなく動いている状態」から計測・改善・維持できる状態に引き上げるための実践的な方法を紹介します。


なぜReact QueryはPerformanceに効くのか

React Queryがパフォーマンスに寄与する理由は主に3つです。

1. キャッシュによる重複リクエストの排除

同じ queryKey のクエリは1回しかフェッチされません。

例:10コンポーネントが同じデータを要求 → APIコールは1回だけ

2. バックグラウンド再フェッチによるUX向上

  • キャッシュを即表示
  • 裏で最新データを取得
  • ローディング時間を大幅に削減

3. 不要な再レンダリングの抑制

変更があった部分のみ再レンダリングされます。select を使うとさらに細かく制御可能です。


改善① — staleTimeを適切に設定する

デフォルトの問題点

const { data } = useQuery({
  queryKey: ['users'],
  queryFn: fetchUsers,
  // staleTime: 0(デフォルト)
});
  • データ取得直後に「stale」扱い
  • タブ切り替えごとに再フェッチ
  • 無駄なリクエストが発生

データに応じた設定

// ほぼ変わらないデータ
useQuery({
  queryKey: ['categories'],
  queryFn: fetchCategories,
  staleTime: 1000 * 60 * 60, // 1時間
});

// 数分ごとに更新
useQuery({
  queryKey: ['dashboard'],
  queryFn: fetchDashboard,
  staleTime: 1000 * 60 * 5, // 5分
});

// リアルタイムデータ
useQuery({
  queryKey: ['notifications'],
  queryFn: fetchNotifications,
  staleTime: 0,
  refetchInterval: 1000 * 30,
});

グローバル設定

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60,
      gcTime: 1000 * 60 * 10,
      retry: 1,
      refetchOnWindowFocus: false,
    },
  },
});

refetchOnWindowFocus: false は多くのアプリで有効です。


改善② — selectで再レンダリングを最適化

問題

function UserIdList() {
  const { data } = useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
  });
  const ids = data?.map(u => u.id);
  return <div>{ids?.join(', ')}</div>;
}

→ 不要な再レンダリングが発生します。

解決

function UserIdList() {
  const { data: ids } = useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
    select: (data) => data.map(u => u.id),
  });
  return <div>{ids?.join(', ')}</div>;
}

select を使うことで必要なデータのみを購読できます。

特定データのみ取得

function useUserName(userId: number) {
  return useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
    select: (data) => data.find(u => u.id === userId)?.name,
  });
}

改善③ — Prefetchingで体感速度を向上

ホバー時にPrefetch

onMouseEnter={() => {
  queryClient.prefetchQuery({
    queryKey: ['posts', post.id],
    queryFn: () => fetchPost(post.id),
    staleTime: 1000 * 60,
  });
}}

クリック時にはデータが取得済みになります。

ページネーション先読み

useEffect(() => {
  queryClient.prefetchQuery({
    queryKey: ['posts', page + 1],
    queryFn: () => fetchPosts(page + 1),
  });
}, [page]);

ページ遷移がスムーズになります。


改善④ — initialDataでローディング削減

initialData: () => {
  const posts = queryClient.getQueryData(['posts']);
  return posts?.find(p => p.id === postId);
},

リストから詳細への遷移でローディングを省略できます。

isLoading と isFetching の違い

状態 意味
isLoading データが存在しない
isFetching バックグラウンドで通信中

改善⑤ — 並列クエリで高速化

自動並列

const { data: users } = useQuery(...)
const { data: posts } = useQuery(...)
const { data: stats } = useQuery(...)

自動で並列実行されます。

動的並列

const results = useQueries({
  queries: userIds.map(id => ({
    queryKey: ['users', id],
    queryFn: () => fetchUser(id),
  })),
});

改善⑥ — DevToolsで可視化

npm install @tanstack/react-query-devtools
<ReactQueryDevtools initialIsOpen={false} />

確認できる内容:

  • キャッシュの中身
  • クエリ状態(fresh / stale / fetching)
  • フェッチ回数
  • GCタイミング

改善は必ず実測で確認することが重要です。


まとめ — 優先順位をつける

優先度 改善項目 効果 難易度
staleTime 無駄なリクエスト削減
refetchOnWindowFocus: false 不要なフェッチ防止
select 再レンダリング削減
Prefetching 体感速度向上
並列クエリ 待ち時間短縮
initialData ローディング削減

まずは以下から取り組むのがおすすめです:

  1. staleTime の設定
  2. refetchOnWindowFocus: false の設定

参考リンク


この記事が役に立ったら LGTM をお願いします 🙏
質問・指摘はコメントで気軽にどうぞ!

7
4
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
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?