jotai-expert

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Jotai Expert

Jotai 专家指南

Jotaiを使用したReact状態管理の実装ガイド。
使用Jotai实现React状态管理的指南。

Core Concepts

核心概念

Atom

Atom

状態の最小単位。値を持たず、Storeに保存される。
typescript
// Primitive atom
const countAtom = atom(0)
const nameAtom = atom('')

// Derived read-only atom
const doubleAtom = atom((get) => get(countAtom) * 2)

// Derived read-write atom
const countWithLabelAtom = atom(
  (get) => `Count: ${get(countAtom)}`,
  (get, set, newValue: number) => set(countAtom, newValue)
)

// Write-only atom (action atom)
const incrementAtom = atom(null, (get, set) => {
  set(countAtom, get(countAtom) + 1)
})
状态的最小单位。本身不存储值,值保存在Store中。
typescript
// Primitive atom
const countAtom = atom(0)
const nameAtom = atom('')

// Derived read-only atom
const doubleAtom = atom((get) => get(countAtom) * 2)

// Derived read-write atom
const countWithLabelAtom = atom(
  (get) => `Count: ${get(countAtom)}`,
  (get, set, newValue: number) => set(countAtom, newValue)
)

// Write-only atom (action atom)
const incrementAtom = atom(null, (get, set) => {
  set(countAtom, get(countAtom) + 1)
})

Hooks

Hooks

typescript
// Read and write
const [value, setValue] = useAtom(countAtom)

// Read only
const value = useAtomValue(countAtom)

// Write only
const setValue = useSetAtom(countAtom)
typescript
// Read and write
const [value, setValue] = useAtom(countAtom)

// Read only
const value = useAtomValue(countAtom)

// Write only
const setValue = useSetAtom(countAtom)

Implementation Patterns

实现模式

Pattern 1: Feature Module

模式1:功能模块

typescript
// atoms/user.ts
const baseUserAtom = atom<User | null>(null)

// Public read-only atom
export const userAtom = atom((get) => get(baseUserAtom))

// Actions
export const setUserAtom = atom(null, (get, set, user: User) => {
  set(baseUserAtom, user)
})

export const clearUserAtom = atom(null, (get, set) => {
  set(baseUserAtom, null)
})
typescript
// atoms/user.ts
const baseUserAtom = atom<User | null>(null)

// Public read-only atom
export const userAtom = atom((get) => get(baseUserAtom))

// Actions
export const setUserAtom = atom(null, (get, set, user: User) => {
  set(baseUserAtom, user)
})

export const clearUserAtom = atom(null, (get, set) => {
  set(baseUserAtom, null)
})

Pattern 2: Async Data Fetching

模式2:异步数据获取

typescript
const userIdAtom = atom<number | null>(null)

// Suspenseと連携する非同期atom
const userDataAtom = atom(async (get) => {
  const userId = get(userIdAtom)
  if (!userId) return null
  const response = await fetch(`/api/users/${userId}`)
  return response.json()
})

// Component
function UserProfile() {
  const userData = useAtomValue(userDataAtom)
  return <div>{userData?.name}</div>
}

// Suspenseでラップ
<Suspense fallback={<Loading />}>
  <UserProfile />
</Suspense>
typescript
const userIdAtom = atom<number | null>(null)

// 与Suspense联动的异步atom
const userDataAtom = atom(async (get) => {
  const userId = get(userIdAtom)
  if (!userId) return null
  const response = await fetch(`/api/users/${userId}`)
  return response.json()
})

// 组件
function UserProfile() {
  const userData = useAtomValue(userDataAtom)
  return <div>{userData?.name}</div>
}

// 用Suspense包裹
<Suspense fallback={<Loading />}>
  <UserProfile />
</Suspense>

Pattern 3: atomFamily

模式3:atomFamily

動的にatomを生成・キャッシュ。メモリリーク対策必須。
typescript
const todoFamily = atomFamily((id: string) =>
  atom({ id, text: '', completed: false })
)

// 使用
const todoAtom = todoFamily('todo-1')

// クリーンアップ
todoFamily.remove('todo-1')

// 自動削除ルール設定
todoFamily.setShouldRemove((createdAt, param) => {
  return Date.now() - createdAt > 60 * 60 * 1000 // 1時間後削除
})
动态生成并缓存atom。必须做好内存泄漏防护。
typescript
const todoFamily = atomFamily((id: string) =>
  atom({ id, text: '', completed: false })
)

// 使用
const todoAtom = todoFamily('todo-1')

// 清理
todoFamily.remove('todo-1')

// 设置自动删除规则
todoFamily.setShouldRemove((createdAt, param) => {
  return Date.now() - createdAt > 60 * 60 * 1000 // 1小时后删除
})

Pattern 4: Persistence

模式4:持久化

typescript
import { atomWithStorage } from 'jotai/utils'

// localStorage永続化
const themeAtom = atomWithStorage('theme', 'light')

// sessionStorage永続化
import { createJSONStorage } from 'jotai/utils'
const sessionAtom = atomWithStorage(
  'session',
  null,
  createJSONStorage(() => sessionStorage)
)
typescript
import { atomWithStorage } from 'jotai/utils'

// localStorage持久化
const themeAtom = atomWithStorage('theme', 'light')

// sessionStorage持久化
import { createJSONStorage } from 'jotai/utils'
const sessionAtom = atomWithStorage(
  'session',
  null,
  createJSONStorage(() => sessionStorage)
)

Pattern 5: Reset

模式5:重置

typescript
import { atomWithReset, useResetAtom, RESET } from 'jotai/utils'

const formAtom = atomWithReset({ name: '', email: '' })

// コンポーネント内
const resetForm = useResetAtom(formAtom)
resetForm() // 初期値に戻る

// 派生atomでRESETシンボル使用
const derivedAtom = atom(
  (get) => get(formAtom),
  (get, set, newValue) => {
    set(formAtom, newValue === RESET ? RESET : newValue)
  }
)
typescript
import { atomWithReset, useResetAtom, RESET } from 'jotai/utils'

const formAtom = atomWithReset({ name: '', email: '' })

// 组件内
const resetForm = useResetAtom(formAtom)
resetForm() // 恢复为初始值

// 在派生atom中使用RESET符号
const derivedAtom = atom(
  (get) => get(formAtom),
  (get, set, newValue) => {
    set(formAtom, newValue === RESET ? RESET : newValue)
  }
)

Performance Optimization

性能优化

selectAtom

selectAtom

大きなオブジェクトから一部のみ取得。派生atomを優先し、必要な場合のみ使用。
typescript
import { selectAtom } from 'jotai/utils'

const personAtom = atom({ name: 'John', age: 30, address: {...} })

// nameのみを購読
const nameAtom = selectAtom(personAtom, (person) => person.name)

// 安定した参照が必要(useMemoまたは外部定義)
const stableNameAtom = useMemo(
  () => selectAtom(personAtom, (p) => p.name),
  []
)
从大对象中仅获取部分数据。优先使用派生atom,仅在必要时使用该方法。
typescript
import { selectAtom } from 'jotai/utils'

const personAtom = atom({ name: 'John', age: 30, address: {...} })

// 仅订阅name
const nameAtom = selectAtom(personAtom, (person) => person.name)

// 需要稳定引用(使用useMemo或外部定义)
const stableNameAtom = useMemo(
  () => selectAtom(personAtom, (p) => p.name),
  []
)

splitAtom

splitAtom

配列の各要素を独立したatomとして管理。
typescript
import { splitAtom } from 'jotai/utils'

const todosAtom = atom<Todo[]>([])
const todoAtomsAtom = splitAtom(todosAtom)

function TodoList() {
  const [todoAtoms, dispatch] = useAtom(todoAtomsAtom)

  return (
    <>
      {todoAtoms.map((todoAtom) => (
        <TodoItem
          key={`${todoAtom}`}
          todoAtom={todoAtom}
          onRemove={() => dispatch({ type: 'remove', atom: todoAtom })}
        />
      ))}
    </>
  )
}
将数组的每个元素作为独立的atom进行管理。
typescript
import { splitAtom } from 'jotai/utils'

const todosAtom = atom<Todo[]>([])
const todoAtomsAtom = splitAtom(todosAtom)

function TodoList() {
  const [todoAtoms, dispatch] = useAtom(todoAtomsAtom)

  return (
    <>
      {todoAtoms.map((todoAtom) => (
        <TodoItem
          key={`${todoAtom}`}
          todoAtom={todoAtom}
          onRemove={() => dispatch({ type: 'remove', atom: todoAtom })}
        />
      ))}
    </>
  )
}

TypeScript

TypeScript

typescript
// 型推論を活用(明示的型定義は不要な場合が多い)
const countAtom = atom(0) // PrimitiveAtom<number>

// 明示的型定義が必要な場合
const userAtom = atom<User | null>(null)

// Write-only atomの型
const actionAtom = atom<null, [string, number], void>(
  null,
  (get, set, str, num) => { ... }
)

// 型抽出
type CountValue = ExtractAtomValue<typeof countAtom> // number
typescript
// 活用类型推断(多数情况下无需显式类型定义)
const countAtom = atom(0) // PrimitiveAtom<number>

// 需要显式类型定义的情况
const userAtom = atom<User | null>(null)

// 只写atom的类型
const actionAtom = atom<null, [string, number], void>(
  null,
  (get, set, str, num) => { ... }
)

// 类型提取
type CountValue = ExtractAtomValue<typeof countAtom> // number

Testing

测试

typescript
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Provider } from 'jotai'
import { useHydrateAtoms } from 'jotai/utils'

// 初期値を注入するヘルパー
function HydrateAtoms({ initialValues, children }) {
  useHydrateAtoms(initialValues)
  return children
}

function TestProvider({ initialValues, children }) {
  return (
    <Provider>
      <HydrateAtoms initialValues={initialValues}>
        {children}
      </HydrateAtoms>
    </Provider>
  )
}

// テスト
test('increments counter', async () => {
  render(
    <TestProvider initialValues={[[countAtom, 5]]}>
      <Counter />
    </TestProvider>
  )

  await userEvent.click(screen.getByRole('button'))
  expect(screen.getByText('6')).toBeInTheDocument()
})
typescript
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Provider } from 'jotai'
import { useHydrateAtoms } from 'jotai/utils'

// 注入初始值的辅助组件
function HydrateAtoms({ initialValues, children }) {
  useHydrateAtoms(initialValues)
  return children
}

function TestProvider({ initialValues, children }) {
  return (
    <Provider>
      <HydrateAtoms initialValues={initialValues}>
        {children}
      </HydrateAtoms>
    </Provider>
  )
}

// 测试
test('increments counter', async () => {
  render(
    <TestProvider initialValues={[[countAtom, 5]]}>
      <Counter />
    </TestProvider>
  )

  await userEvent.click(screen.getByRole('button'))
  expect(screen.getByText('6')).toBeInTheDocument()
})

Debugging

调试

typescript
// デバッグラベル追加
countAtom.debugLabel = 'count'

// useAtomsDebugValueでProvider内の全atom確認
import { useAtomsDebugValue } from 'jotai-devtools'
function DebugObserver() {
  useAtomsDebugValue()
  return null
}

// Redux DevTools連携
import { useAtomDevtools } from 'jotai-devtools'
useAtomDevtools(countAtom, { name: 'count' })
typescript
// 添加调试标签
countAtom.debugLabel = 'count'

// 使用useAtomsDebugValue查看Provider内的所有atom
import { useAtomsDebugValue } from 'jotai-devtools'
function DebugObserver() {
  useAtomsDebugValue()
  return null
}

// 与Redux DevTools联动
import { useAtomDevtools } from 'jotai-devtools'
useAtomDevtools(countAtom, { name: 'count' })

Best Practices

最佳实践

  1. Atom粒度: 小さく再利用可能な単位に分割
  2. カプセル化: base atomを隠蔽し、派生atomのみをexport
  3. Action atom: 複雑な更新ロジックはwrite-only atomに分離
  4. 非同期処理: SuspenseとError Boundaryを適切に配置
  5. atomFamily: メモリリーク対策として
    remove()
    または
    setShouldRemove()
    を使用
  6. TypeScript: 型推論を活用し、必要な場合のみ明示的に型定義
  7. テスト: ユーザー操作に近い形でテストを記述
  1. Atom粒度: 拆分为小且可复用的单元
  2. 封装: 隐藏base atom,仅导出派生atom
  3. Action atom: 将复杂的更新逻辑分离到只写atom中
  4. 异步处理: 合理配置Suspense和Error Boundary
  5. atomFamily: 使用
    remove()
    setShouldRemove()
    防止内存泄漏
  6. TypeScript: 活用类型推断,仅在必要时显式定义类型
  7. 测试: 以贴近用户操作的方式编写测试

References

参考资料

詳細は以下を参照:
  • patterns.md: 高度な実装パターン
  • api.md: API詳細リファレンス
详情请参考以下内容:
  • patterns.md: 高级实现模式
  • api.md: API详细参考