jotai-expert について
jotai-expert は、Jotai を使った React の状態管理を実装・レビューするスキルです。Atom とフックの選択、パフォーマンスパターン、非同期状態、実装上の注意点を扱います。
主な機能
- 01Atom の種類とフックの選択
- 02細粒度の購読
- 03リストやオブジェクト状態の効率的な管理
- 04loadable による非同期状態の処理
- 05よくあるアンチパターンの指針
活用シーン
- 01共有 React 状態の管理方法の選定
- 02コンポーネントの再レンダリング削減
- 03大規模なフォームやリストの管理
- 04非同期 Atom データの処理
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ s-hiraoku/ skills-factory --skill jotai-expertインストール後、Claude Code で/jotai-expertを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/core-api.mdpatterns.mdperformance.mdutilities.md詳細
Jotai エキスパート
Jotai は、アトミックなアプローチを採用した、プリミティブで柔軟な React 用状態管理ライブラリです。最新の API 詳細については、常に https://jotai.org/ を参照してください。
判断ツリー
Need state management?
├── Simple local state → useState (no Jotai needed)
├── Shared state across components
│ ├── Few components, same tree → Context may suffice
│ └── Many components, complex → Use Jotai ✓
└── Global app state → Use Jotai ✓
Choosing atom type:
├── Static value → atom(initialValue)
├── Computed from other atoms → atom((get) => ...)
├── Need to modify other atoms → atom(null, (get, set) => ...)
├── Persist to storage → atomWithStorage()
├── List of items → splitAtom() or atoms-in-atom
└── Parameterized data → atomFamily()
Performance issue?
├── Component re-renders too often
│ ├── Only reading? → useAtomValue
│ ├── Only writing? → useSetAtom
│ ├── Large object? → selectAtom / focusAtom
│ └── List items? → splitAtom
└── Async loading states → loadable() or manual loading atoms
コアパターン
Atom の種類
import { atom } from 'jotai'
// 1. Primitive: holds value
const countAtom = atom(0)
// 2. Derived read-only: computed from others
const doubleAtom = atom((get) => get(countAtom) * 2)
// 3. Derived read-write: custom setter
const celsiusAtom = atom(0)
const fahrenheitAtom = atom(
(get) => get(celsiusAtom) * 9/5 + 32,
(get, set, newF: number) => set(celsiusAtom, (newF - 32) * 5/9)
)
// 4. Write-only (action): no read value
const incrementAtom = atom(null, (get, set) => {
set(countAtom, get(countAtom) + 1)
})
フックの選択
| 必要な操作 | フック | 変更時に再レンダリング |
|---|---|---|
| 読み取りのみ | useAtomValue(atom) | はい |
| 書き込みのみ | useSetAtom(atom) | いいえ |
| 読み書き両方 | useAtom(atom) | はい |
参照の安定性
// ❌ WRONG: Creates new atom every render
function Component() {
const myAtom = atom(0) // Unstable reference
}
// ✅ CORRECT: Define outside component
const myAtom = atom(0)
function Component() {
const [value] = useAtom(myAtom)
}
// ✅ CORRECT: useMemo for dynamic atoms
function Component({ id }) {
const itemAtom = useMemo(() => atom(items[id]), [id])
}
パフォーマンスの最適化
1. 粒度の細かいサブスクリプション
// ❌ BAD: Re-renders on any user field change
function UserProfile() {
const [user] = useAtom(userAtom)
return <h1>{user.name}</h1>
}
// ✅ GOOD: Only re-renders when name changes
import { selectAtom } from 'jotai/utils'
const nameAtom = selectAtom(userAtom, (u) => u.name)
function UserName() {
const name = useAtomValue(nameAtom)
return <h1>{name}</h1>
}
2. splitAtom を使った効率的なリスト
import { splitAtom } from 'jotai/utils'
const todosAtom = atom<Todo[]>([])
const todoAtomsAtom = splitAtom(todosAtom, (t) => t.id)
function TodoList() {
const [todoAtoms] = useAtom(todoAtomsAtom)
return todoAtoms.map((todoAtom) => (
<TodoItem key={todoAtom.toString()} atom={todoAtom} />
))
}
// Each item updates independently
function TodoItem({ atom }) {
const [todo, setTodo] = useAtom(atom)
// Changes here don't re-render other items
}
3. focusAtom を使った大きなオブジェクトの処理
import { focusAtom } from 'jotai-optics'
const formAtom = atom({ name: '', email: '', address: { city: '' } })
// Focused atoms for each field
const nameAtom = focusAtom(formAtom, (o) => o.prop('name'))
const cityAtom = focusAtom(formAtom, (o) => o.prop('address').prop('city'))
// Each field component only re-renders when its value changes
4. loadable を使った非同期処理
import { loadable } from 'jotai/utils'
const asyncDataAtom = atom(async () => fetch('/api').then(r => r.json()))
const loadableDataAtom = loadable(asyncDataAtom)
function Component() {
const data = useAtomValue(loadableDataAtom)
if (data.state === 'loading') return <Spinner />
if (data.state === 'hasError') return <Error />
return <Data value={data.data} />
}
避けるべきアンチパターン
- コンポーネント内での重い計算:atom の読み取り関数またはアクションに移動します
- レンダー時の atom の作成:コンポーネントの外で定義するか、useMemo を使います
- 読み取りまたは書き込みのみの場合に useAtom を使うこと:useAtomValue/useSetAtom を使います
- 頻繁に更新されるデータの過度な細分化:関連する更新をまとめます
- 非同期 atom ごとのネストされた Suspense:適切な境界または loadable を使います
参考資料
トピック別に整理された詳細なドキュメント:
- references/core-api.md:atom、フック、Store、Provider API の詳細
- references/utilities.md:atomWithStorage、loadable、splitAtom、selectAtom、atomFamily
- references/performance.md:最適化戦略、レンダー制御、大きなオブジェクトの処理
- references/patterns.md:構成、非同期パターン、テスト、TypeScript、SSR、デバッグ
Jotai のコードを実装またはレビューする際は、詳細なガイダンスを確認するために、該当する参考ファイルを読み込んでください。
コメント
Markdown 対応