generic-react-feature-developer について
Reactの機能開発に向け、アーキテクチャ、状態管理、データ取得、永続化、コンポーネント設計の指針を示すスキルです。Reactアプリを構築・拡張する開発者を対象としています。
主な機能
- 01Reactプロジェクト構成の指針
- 02ZustandとContextの状態管理パターン
- 03React Queryによるサーバー状態管理
- 04IndexedDBサービスの設計パターン
- 05遅延読み込みとコード分割
- 06カスタムフックとコンポーネント構成
活用シーン
- 01機能ごとの状態管理方式の計画
- 02大容量データやバイナリデータの永続化
- 03コンポーネントの遅延読み込みによる初期負荷の軽減
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ travisjneuman/ .claude --skill generic-react-feature-developerインストール後、Claude Code で/generic-react-feature-developerを入力して読み込まれたか確認します。
詳細
React 機能開発者
React のアーキテクチャパターンに沿って、機能開発を進めます。
拡張元: 汎用機能開発者 - 開発ワークフロー、スコープの評価、構築と統合の判断については、基本スキルをお読みください。
React アーキテクチャ
プロジェクト構成
src/
├── components/
│ ├── ui/ # Reusable primitives (Button, Input)
│ ├── features/ # Feature-specific components
│ └── layout/ # Layout components (Header, Sidebar)
├── hooks/ # Custom hooks (useAuth, useStore)
├── stores/ # Zustand stores
├── services/ # API clients, IndexedDB wrappers
├── types/ # TypeScript interfaces
└── lib/ # Utilities
状態管理パターン
Zustand ストア(推奨)
// stores/useFeatureStore.ts
interface FeatureState {
items: Item[];
isLoading: boolean;
// Actions
addItem: (item: Item) => void;
removeItem: (id: string) => void;
}
const useFeatureStore = create<FeatureState>()(
persist(
(set) => ({
items: [],
isLoading: false,
addItem: (item) => set((s) => ({ items: [...s.items, item] })),
removeItem: (id) =>
set((s) => ({
items: s.items.filter((i) => i.id !== id),
})),
}),
{
name: "feature-storage",
version: 1,
migrate: (state, version) => {
// Handle migrations between versions
return state as FeatureState;
},
},
),
);
Zustand セレクター(パフォーマンス)
// Avoid re-renders with selectors
const items = useFeatureStore((state) => state.items);
const addItem = useFeatureStore((state) => state.addItem);
// Shallow compare for objects
import { shallow } from "zustand/shallow";
const { items, isLoading } = useFeatureStore(
(state) => ({ items: state.items, isLoading: state.isLoading }),
shallow,
);
Context と Zustand の使い分け
| Context を使う場合 | Zustand を使う場合 |
|---|---|
| テーマ、ロケール(変更頻度が低い) | 頻繁に更新されるデータ |
| 認証状態 | アクションを伴う複雑な状態 |
| プロバイダーがすでに存在する場合 | 永続化が必要な場合 |
| 1~2 階層のプロップのバケツリレー | 複数の領域に関わる懸念事項 |
サーバー状態(React Query)
// Server state - React Query
const { data, isLoading, error } = useQuery({
queryKey: ["items", userId],
queryFn: () => fetchItems(userId),
staleTime: 5 * 60 * 1000, // 5 minutes
});
// Mutations
const mutation = useMutation({
mutationFn: createItem,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["items"] });
},
});
IndexedDB の統合
使用するタイミング
| シナリオ | 解決策 |
|---|---|
| 合計 5 MB 未満 | Zustand persist 経由の localStorage |
| 合計 5 MB 超 | IndexedDB |
| バイナリデータ(画像、ファイル) | IndexedDB |
| シンプルなキーと値 | localStorage |
| 複雑なクエリ | IndexedDB |
サービスラッパーパターン
// services/indexedDBService.ts
class IndexedDBService {
private db: IDBDatabase | null = null;
async init() {
return new Promise<void>((resolve, reject) => {
const request = indexedDB.open("AppDB", 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve();
};
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
db.createObjectStore("items", { keyPath: "id" });
};
});
}
async setItem<T>(store: string, value: T): Promise<void> {
// Implementation
}
async getItem<T>(store: string, key: string): Promise<T | null> {
// Implementation
}
}
export const indexedDBService = new IndexedDBService();
遅延読み込み
コンポーネントの遅延読み込み
// Heavy components (>20KB)
const HeavyChart = lazy(() => import('./HeavyChart'));
const RichTextEditor = lazy(() => import('./RichTextEditor'));
// Pages
const SettingsPage = lazy(() => import('./pages/Settings'));
// Usage with Suspense
<Suspense fallback={<Skeleton />}>
<HeavyChart data={data} />
</Suspense>
ルートレベルのコード分割
// React Router example
const routes = [
{
path: '/dashboard',
element: <DashboardLayout />,
children: [
{
path: 'settings',
lazy: () => import('./pages/Settings'),
},
],
},
];
カスタムフックのパターン
機能フック
// hooks/useItems.ts
function useItems() {
const items = useFeatureStore((s) => s.items);
const addItem = useFeatureStore((s) => s.addItem);
const sortedItems = useMemo(
() => [...items].sort((a, b) => b.createdAt - a.createdAt),
[items],
);
return { items: sortedItems, addItem };
}
複合フック(複数のソースの組み合わせ)
// hooks/useDashboard.ts
function useDashboard() {
// Local state
const [filter, setFilter] = useState("all");
// Server state
const { data: items } = useQuery({ queryKey: ["items"] });
// Client state
const preferences = usePreferencesStore((s) => s.dashboard);
// Derived
const filteredItems = useMemo(
() => items?.filter((i) => filter === "all" || i.status === filter),
[items, filter],
);
return { filter, setFilter, items: filteredItems, preferences };
}
コンポーネントのコンポジション
複合コンポーネント
// Usage: <Tabs><Tabs.List /><Tabs.Panel /></Tabs>
const TabsContext = createContext<TabsContextValue | null>(null);
function Tabs({ children, defaultValue }: TabsProps) {
const [active, setActive] = useState(defaultValue);
return (
<TabsContext.Provider value={{ active, setActive }}>
{children}
</TabsContext.Provider>
);
}
Tabs.List = function TabsList({ children }: { children: ReactNode }) {
return <div role="tablist">{children}</div>;
};
Tabs.Panel = function TabsPanel({ value, children }: TabsPanelProps) {
const { active } = useContext(TabsContext)!;
if (value !== active) return null;
return <div role="tabpanel">{children}</div>;
};
React 機能チェックリスト
開始前:
- プロジェクトのパターンを把握するために CLAUDE.md を読む
- 再利用できる既存のコンポーネントを確認する
- 状態管理の方針を計画する
- バンドルサイズへの影響を見積もる
開発中:
- プロジェクトのデザインシステムに従う
- TypeScript の strict モードを使用する
- キーボード操作を実装する
- ARIA ラベルを追加する
- ダークモードに対応する
完了前:
- ユーザーから依頼された場合にのみユニットテストを作成する
- 重いコンポーネントを遅延読み込みする
- 新しいインポートと依存関係によるバンドルへの影響を確認する
- code-reviewer スキルを使ってレビューする
関連項目
コメント
Markdown 対応