swr
SWR(stale-while-revalidate)を活用したReact Hooksによるデータ取得・キャッシュ・再検証を効率的に行うためのガイドラインを提供します。データフェッチングの実装やキャッシュ戦略の最適化が必要な場面で活用できます。
description の原文を見る
Guidelines for using SWR (stale-while-revalidate) React Hooks for efficient data fetching, caching, and revalidation
SKILL.md 本文
SWR ベストプラクティス
あなたは SWR (stale-while-revalidate)、TypeScript、React 開発の専門家です。SWR は React Hooks ライブラリで、キャッシュからデータを返し (stale)、次にリクエストを送信 (revalidate) し、最後に最新のデータを提供するデータ取得ライブラリです。
コア原則
- クライアント側のすべてのデータ取得に SWR を使用する
- 自動キャッシングと再検証を活用する
- SWR の組み込み状態管理でボイラープレートを最小化する
- 適切なエラーハンドリングとローディング状態を実装する
- TypeScript を使用して完全な型安全性を確保する
主な機能
- Stale-While-Revalidate: キャッシュされたデータを即座に返し、バックグラウンドで再検証
- 自動再検証: マウント時、ウィンドウフォーカス時、ネットワーク再接続時に自動再検証
- リクエストの重複排除: 同じキーを使用する複数のコンポーネントが 1 つのリクエストを共有
- 組み込みキャッシング: ゼロコンフィグレーションキャッシング、スマート無効化対応
- 最小限の API: シンプルなフック基盤のインターフェース
プロジェクト構造
src/
hooks/
swr/
useUser.ts
usePosts.ts
useProducts.ts
lib/
fetcher.ts # グローバル fetcher 設定
providers/
SWRProvider.tsx # SWR 設定プロバイダー
types/
api.ts # API レスポンス型
セットアップと設定
グローバル設定
// providers/SWRProvider.tsx
import { SWRConfig } from 'swr';
const fetcher = async (url: string) => {
const res = await fetch(url);
if (!res.ok) {
const error = new Error('An error occurred while fetching data.');
throw error;
}
return res.json();
};
export function SWRProvider({ children }: { children: React.ReactNode }) {
return (
<SWRConfig
value={{
fetcher,
revalidateOnFocus: true,
revalidateOnReconnect: true,
shouldRetryOnError: true,
errorRetryCount: 3,
dedupingInterval: 2000,
}}
>
{children}
</SWRConfig>
);
}
カスタム Fetcher
// lib/fetcher.ts
export async function fetcher<T>(url: string): Promise<T> {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
// 認証付き
export async function authFetcher<T>(url: string): Promise<T> {
const token = getAuthToken();
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
基本的な使い方
useSWR フック
useSWR フックは 3 つのパラメータを受け入れます:
- key: リクエストの一意の文字列識別子(URL など)
- fetcher: データを取得する非同期関数
- options: 設定オプション
import useSWR from 'swr';
interface User {
id: string;
name: string;
email: string;
}
function useUser(userId: string) {
const { data, error, isLoading, isValidating, mutate } = useSWR<User>(
userId ? `/api/users/${userId}` : null,
fetcher
);
return {
user: data,
isLoading,
isError: !!error,
isValidating,
mutate,
};
}
条件付き取得
キーとして null またはフォルシーな値を渡して、条件付きで取得をスキップします:
// userId が利用可能な場合のみ取得
const { data } = useSWR(userId ? `/api/users/${userId}` : null, fetcher);
// 動的キーのための関数を使用
const { data } = useSWR(() => `/api/users/${userId}`, fetcher);
再検証戦略
自動再検証
SWR は以下の 3 つのケースで自動的にデータを再検証します:
- コンポーネントがマウントされる(キャッシュデータがある場合でも)
- ウィンドウがフォーカスを取得する
- ブラウザがネットワーク接続を回復する
// 特定の再検証動作を無効化する
const { data } = useSWR('/api/data', fetcher, {
revalidateOnFocus: false,
revalidateOnReconnect: false,
revalidateOnMount: true,
});
手動再検証
import { useSWRConfig } from 'swr';
function UpdateButton() {
const { mutate } = useSWRConfig();
const handleUpdate = async () => {
// 特定のキーを再検証
await mutate('/api/users');
// フィルターに一致するすべてのキーを再検証
await mutate(
(key) => typeof key === 'string' && key.startsWith('/api/users'),
undefined,
{ revalidate: true }
);
};
return <button onClick={handleUpdate}>Refresh</button>;
}
ポーリング / インターバルリフレッシュ
// 3 秒ごとにリフレッシュ
const { data } = useSWR('/api/realtime', fetcher, {
refreshInterval: 3000,
});
// 条件付きポーリング
const { data } = useSWR('/api/realtime', fetcher, {
refreshInterval: isVisible ? 1000 : 0,
});
ミューテーションパターン
楽観的更新
import useSWR, { useSWRConfig } from 'swr';
function useUpdateUser() {
const { mutate } = useSWRConfig();
const updateUser = async (userId: string, newData: Partial<User>) => {
// 楽観的更新
await mutate(
`/api/users/${userId}`,
async (currentData: User | undefined) => {
// API を更新
const updated = await fetch(`/api/users/${userId}`, {
method: 'PATCH',
body: JSON.stringify(newData),
}).then(r => r.json());
return updated;
},
{
optimisticData: (current) => ({ ...current, ...newData } as User),
rollbackOnError: true,
populateCache: true,
revalidate: false,
}
);
};
return { updateUser };
}
バウンドミューテート
function UserProfile({ userId }: { userId: string }) {
const { data: user, mutate } = useSWR(`/api/users/${userId}`, fetcher);
const handleUpdate = async (newName: string) => {
// ローカルデータを即座に更新
mutate({ ...user, name: newName }, false);
// サーバーに更新を送信
await updateUserName(userId, newName);
// 一貫性を保証するために再検証
mutate();
};
return (
<div>
<p>{user?.name}</p>
<button onClick={() => handleUpdate('New Name')}>Update</button>
</div>
);
}
ページング
基本的なページング
function usePaginatedUsers(page: number) {
return useSWR(`/api/users?page=${page}`, fetcher, {
keepPreviousData: true,
});
}
useSWRInfinite を使用した無限ローディング
import useSWRInfinite from 'swr/infinite';
function useInfiniteUsers() {
const getKey = (pageIndex: number, previousPageData: User[] | null) => {
// null を返して取得を停止
if (previousPageData && previousPageData.length === 0) return null;
// 次のページのキーを返す
return `/api/users?page=${pageIndex + 1}`;
};
const { data, error, size, setSize, isValidating } = useSWRInfinite(
getKey,
fetcher
);
const users = data ? data.flat() : [];
const isLoadingMore = size > 0 && data && typeof data[size - 1] === 'undefined';
const isEmpty = data?.[0]?.length === 0;
const isReachingEnd = isEmpty || (data && data[data.length - 1]?.length < 10);
return {
users,
isLoadingMore,
isReachingEnd,
loadMore: () => setSize(size + 1),
};
}
カーソルベースのページング
import useSWRInfinite from 'swr/infinite';
function useCursorPagination() {
const getKey = (pageIndex: number, previousPageData: any) => {
if (previousPageData && !previousPageData.nextCursor) return null;
if (pageIndex === 0) return '/api/items';
return `/api/items?cursor=${previousPageData.nextCursor}`;
};
return useSWRInfinite(getKey, fetcher);
}
エラーハンドリング
コンポーネントレベルのエラーハンドリング
function UserProfile({ userId }: { userId: string }) {
const { data, error, isLoading } = useSWR(`/api/users/${userId}`, fetcher);
if (isLoading) return <Skeleton />;
if (error) {
return (
<ErrorMessage
message="Failed to load user profile"
retry={() => mutate(`/api/users/${userId}`)}
/>
);
}
return <ProfileCard user={data} />;
}
グローバルエラーハンドラー
<SWRConfig
value={{
onError: (error, key) => {
if (error.status !== 403 && error.status !== 404) {
// エラー追跡サービスにエラーを送信
reportError(error, key);
}
},
}}
>
<App />
</SWRConfig>
プリフェッチング
import { preload } from 'swr';
// コンポーネントがレンダリングされる前にデータをプリフェッチ
function prefetchUser(userId: string) {
preload(`/api/users/${userId}`, fetcher);
}
// 使用例:ホバーまたはルート変更時に呼び出し
function UserLink({ userId }: { userId: string }) {
return (
<Link
to={`/users/${userId}`}
onMouseEnter={() => prefetchUser(userId)}
>
View Profile
</Link>
);
}
キー命名規則
- クライアント側のデータ取得には SWR を使用し、最小限の設定で実装する
- ローディング状態とエラーメッセージの条件付きレンダリングを実装する
- データを検証して、予期された形式を確認し、エラーを適切にハンドリングする
- プリフェッチングを使用してユーザーがデータをリクエストする前にパフォーマンスを向上させる
- 自動再検証を活用して、リアルタイムデータの鮮度を確保する
- ページング無限スクロールパターンには
useSWRInfiniteを使用する
Next.js との統合
// サーバー側のデータの場合は getServerSideProps または getStaticProps を使用
// クライアント側のキャッシングと再検証の場合は SWR を使用
// pages/user/[id].tsx
export async function getStaticProps({ params }) {
const user = await fetchUser(params.id);
return { props: { fallback: { [`/api/users/${params.id}`]: user } } };
}
export default function UserPage({ fallback }) {
return (
<SWRConfig value={{ fallback }}>
<UserProfile />
</SWRConfig>
);
}
避けるべきアンチパターン
- SWR で対応できる場合、データ取得に
useEffectを使用しない - エラー状態を無視しない - ユーザーへのフィードバックを常に提供する
- ローディング状態の処理を忘れない
- WebSocket または SSE がより効率的な場合、ポーリングを使用しない
- 依存データの条件付き取得をスキップしない
- 取得されたデータの TypeScript 型を無視しない
ライセンス: Apache-2.0(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- mindrally
- リポジトリ
- mindrally/skills
- ライセンス
- Apache-2.0
- 最終更新
- 不明
Source: https://github.com/mindrally/skills / ライセンス: Apache-2.0
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。