jotai-expert
1
总安装量
1
周安装量
#51023
全站排名
安装命令
npx skills add https://github.com/s-hiraoku/claude-code-harnesses-factory --skill jotai-expert
Agent 安装分布
mcpjam
1
openhands
1
kilo
1
zencoder
1
crush
1
Skill 文档
Jotai Expert
Jotaiã使ç¨ããReactç¶æ 管çã®å®è£ ã¬ã¤ãã
Core Concepts
Atom
ç¶æ ã®æå°åä½ãå¤ãæãããStoreã«ä¿åãããã
// 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
// 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
// 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
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>
Pattern 3: atomFamily
åçã«atomãçæã»ãã£ãã·ã¥ãã¡ã¢ãªãªã¼ã¯å¯¾çå¿ é ã
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
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
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
大ããªãªãã¸ã§ã¯ãããä¸é¨ã®ã¿åå¾ãæ´¾çatomãåªå ããå¿ è¦ãªå ´åã®ã¿ä½¿ç¨ã
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
é åã®åè¦ç´ ãç¬ç«ããatomã¨ãã¦ç®¡çã
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
// 忍è«ãæ´»ç¨ï¼æç¤ºçåå®ç¾©ã¯ä¸è¦ãªå ´åãå¤ãï¼
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
Testing
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
// ãããã°ã©ãã«è¿½å
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
- Atomç²åº¦: å°ããåå©ç¨å¯è½ãªåä½ã«åå²
- ã«ãã»ã«å: base atomãé è½ããæ´¾çatomã®ã¿ãexport
- Action atom: è¤éãªæ´æ°ãã¸ãã¯ã¯write-only atomã«åé¢
- éåæå¦ç: Suspenseã¨Error Boundaryãé©åã«é ç½®
- atomFamily: ã¡ã¢ãªãªã¼ã¯å¯¾çã¨ãã¦
remove()ã¾ãã¯setShouldRemove()ãä½¿ç¨ - TypeScript: 忍è«ãæ´»ç¨ããå¿ è¦ãªå ´åã®ã¿æç¤ºçã«åå®ç¾©
- ãã¹ã: ã¦ã¼ã¶ã¼æä½ã«è¿ãå½¢ã§ãã¹ããè¨è¿°
References
詳細ã¯ä»¥ä¸ãåç §:
- patterns.md: é«åº¦ãªå®è£ ãã¿ã¼ã³
- api.md: API詳細ãªãã¡ã¬ã³ã¹