원시적이고 유연한 React 상태 관리
npm install jotai
현재는 로컬 상태로 구현되어 있습니다. 패키지 설치 후 코드 주석을 해제하세요.
할 일이 없습니다
import { atom, useAtom } from 'jotai';
// 원시 atom (primitive)
const todosAtom = atom<Todo[]>([]);
const filterAtom = atom<'all' | 'active' | 'completed'>('all');
// 파생 atom (derived) - 읽기 전용
const filteredTodosAtom = atom((get) => {
const todos = get(todosAtom);
const filter = get(filterAtom);
if (filter === 'active') return todos.filter(t => !t.completed);
if (filter === 'completed') return todos.filter(t => t.completed);
return todos;
});
// 통계 atom (읽기 전용)
const todoStatsAtom = atom((get) => {
const todos = get(todosAtom);
return {
total: todos.length,
completed: todos.filter(t => t.completed).length,
active: todos.filter(t => !t.completed).length,
};
});
// 컴포넌트에서 사용
function TodoList() {
const [todos, setTodos] = useAtom(todosAtom);
const filteredTodos = useAtomValue(filteredTodosAtom);
return <div>...</div>;
}import { atom, useSetAtom } from 'jotai';
// 읽기/쓰기 atom
const addTodoAtom = atom(
null, // 읽기는 사용 안 함
(get, set, text: string) => {
const todos = get(todosAtom);
set(todosAtom, [
...todos,
{ id: Date.now(), text, completed: false }
]);
}
);
const toggleTodoAtom = atom(
null,
(get, set, id: number) => {
const todos = get(todosAtom);
set(
todosAtom,
todos.map(t => t.id === id ? { ...t, completed: !t.completed } : t)
);
}
);
// 컴포넌트에서 사용 (리렌더링 없음)
function TodoInput() {
const addTodo = useSetAtom(addTodoAtom);
const handleSubmit = (text: string) => {
addTodo(text);
};
return <form onSubmit={...}>...</form>;
}import { atomWithStorage, atomFamily, selectAtom } from 'jotai/utils';
// localStorage 동기화
const todosAtom = atomWithStorage<Todo[]>('jotai-todos', []);
// 동적 atom 생성 (ID별 캐싱)
const userAtomFamily = atomFamily((userId: number) =>
atom(async () => {
const res = await fetch(`/api/users/${userId}`);
return res.json();
})
);
// 특정 필드만 구독
const userNameAtom = selectAtom(
userAtom,
(user) => user.name
);
// 컴포넌트에서 사용
function UserProfile({ userId }: { userId: number }) {
const [user] = useAtom(userAtomFamily(userId));
return <div>{user.name}</div>;
}import { atom, useAtomValue } from 'jotai';
import { Suspense } from 'react';
// 비동기 atom
const userAtom = atom(async () => {
const response = await fetch('/api/user');
return response.json();
});
// 의존적인 비동기 atom
const userPostsAtom = atom(async (get) => {
const user = await get(userAtom);
const response = await fetch(`/api/users/${user.id}/posts`);
return response.json();
});
// Suspense와 함께 사용
function UserPosts() {
const posts = useAtomValue(userPostsAtom);
return (
<div>
{posts.map(post => (
<div key={post.id}>{post.title}</div>
))}
</div>
);
}
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<UserPosts />
</Suspense>
);
}