AgentSkillsCN

jotai-expert

资深专家为您解读 React 应用中的 Jotai 状态管理方案。提供最佳实践、性能优化与架构设计模式。当您需要设计原子结构、实施状态管理、优化重新渲染、处理异步状态、与 TypeScript 集成,或审查 Jotai 代码以排查性能问题时,此技能将为您提供专业指导。适用于涉及 Jotai 原子、衍生状态、focusAtom、splitAtom、atomFamily,或状态管理架构决策的任务。

SKILL.md
--- frontmatter
name: jotai-expert
description: Expert guidance for Jotai state management in React applications. Provides best practices, performance optimization, and architectural patterns. Use when designing atom structures, implementing state management, optimizing re-renders, handling async state, integrating with TypeScript, or reviewing Jotai code for performance issues. Triggers on tasks involving Jotai atoms, derived state, focusAtom, splitAtom, atomFamily, or state management architecture decisions.

Jotai Expert

Jotai is a primitive and flexible state management library for React using an atomic approach. Always reference https://jotai.org/ for the latest API details.

Decision Tree

code
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

Core Patterns

Atom Types

typescript
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)
})

Hook Selection

NeedHookRe-renders on change
Read onlyuseAtomValue(atom)Yes
Write onlyuseSetAtom(atom)No
BothuseAtom(atom)Yes

Reference Stability

typescript
// ❌ 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])
}

Performance Optimization

1. Granular Subscriptions

typescript
// ❌ 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. Efficient Lists with splitAtom

typescript
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. Large Objects with focusAtom

typescript
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. Async with loadable

typescript
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} />
}

Anti-Patterns to Avoid

  1. Heavy computation in components: Move to atom read functions or actions
  2. Creating atoms in render: Define outside or use useMemo
  3. Using useAtom when only reading/writing: Use useAtomValue/useSetAtom
  4. Over-fragmenting frequently-updated data: Batch related updates
  5. Nested Suspense for every async atom: Use strategic boundaries or loadable

References

Detailed documentation organized by topic:

When implementing or reviewing Jotai code, load the relevant reference file for detailed guidance.