동일한 기능을 3가지 라이브러리로 구현한 코드 비교
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
increment: () =>
set((state) => ({
count: state.count + 1
})),
}));
// 사용
const count = useStore(
(state) => state.count
);import { atom } from 'recoil';
const countState = atom({
key: 'countState',
default: 0,
});
// 사용
const [count, setCount] =
useRecoilState(countState);
setCount(count + 1);import { atom } from 'jotai';
const countAtom = atom(0);
// 사용
const [count, setCount] =
useAtom(countAtom);
setCount(count + 1);const useStore = create((set) => ({
todos: [],
// 수동으로 계산
}));
// 컴포넌트에서 계산
const completedCount =
useStore(state =>
state.todos.filter(
t => t.completed
).length
);⚠️ 수동 계산 필요 (useMemo 권장)
const completedCountState =
selector({
key: 'completedCount',
get: ({ get }) => {
const todos =
get(todosState);
return todos.filter(
t => t.completed
).length;
},
});
// 자동 메모이제이션
const count = useRecoilValue(
completedCountState
);✅ Selector로 자동 계산
const completedCountAtom =
atom((get) => {
const todos =
get(todosAtom);
return todos.filter(
t => t.completed
).length;
});
// 자동 메모이제이션
const count = useAtomValue(
completedCountAtom
);✅ 파생 Atom으로 자동 계산
const useStore = create((set) => ({
user: null,
loading: false,
fetchUser: async (id) => {
set({ loading: true });
const res = await fetch(
`/api/users/${id}`
);
const user = await res.json();
set({
user,
loading: false
});
},
}));⚠️ 로딩/에러 상태 수동 관리
const userQuery = selector({
key: 'userQuery',
get: async ({ get }) => {
const userId =
get(userIdState);
const res = await fetch(
`/api/users/${userId}`
);
return res.json();
},
});
// Suspense와 함께
<Suspense fallback="Loading">
<UserComponent />
</Suspense>✅ Suspense 완벽 지원
const userAtom = atom(
async (get) => {
const userId =
get(userIdAtom);
const res = await fetch(
`/api/users/${userId}`
);
return res.json();
}
);
// Suspense와 함께
<Suspense fallback="Loading">
<UserComponent />
</Suspense>✅ Suspense 완벽 지원
import {
devtools,
persist
} from 'zustand/middleware';
const useStore = create()(
devtools(
persist(
(set) => ({
todos: [],
}),
{ name: 'todos' }
)
)
);const todosState = atom({
key: 'todos',
default: [],
effects: [
({ setSelf, onSet }) => {
// localStorage 동기화
const saved =
localStorage.getItem('todos');
if (saved) {
setSelf(JSON.parse(saved));
}
onSet((newValue) => {
localStorage.setItem(
'todos',
JSON.stringify(newValue)
);
});
},
],
});import {
atomWithStorage,
atomFamily,
selectAtom,
} from 'jotai/utils';
const todosAtom =
atomWithStorage(
'todos',
[]
);
const userFamily =
atomFamily((id) =>
atom(async () => {
const res = await fetch(
`/api/users/${id}`
);
return res.json();
})
);interface TodoStore {
todos: Todo[];
addTodo: (text: string)
=> void;
}
const useStore =
create<TodoStore>()(
(set) => ({
todos: [],
addTodo: (text) =>
set((state) => ({
todos: [
...state.todos,
{ id: 1, text }
],
})),
})
);✅ 명시적 타입 정의 필요
const todosState =
atom<Todo[]>({
key: 'todos',
default: [],
});
const completedCount =
selector<number>({
key: 'completedCount',
get: ({ get }) => {
// 타입 자동 추론
const todos =
get(todosState);
return todos.length;
},
});✅ 제네릭으로 타입 지정
// 타입 자동 추론 우수
const todosAtom =
atom<Todo[]>([]);
const completedAtom =
atom((get) => {
const todos =
get(todosAtom);
// number로 자동 추론
return todos.filter(
t => t.completed
).length;
});
// 타입 자동 추론
const [count] =
useAtom(completedAtom);✅ 타입 추론 가장 우수
| 항목 | 🐻 Zustand | ⚛️ Recoil | 👻 Jotai |
|---|---|---|---|
| 번들 사이즈 | ~3KB ✅ | ~21KB ⚠️ | ~3KB ✅ |
| 러닝 커브 | 낮음 ✅ | 중간 ⚠️ | 중간 ⚠️ |
| 파생 상태 | 수동 계산 ⚠️ | Selector ✅ | 파생 Atom ✅ |
| 비동기 처리 | 수동 ⚠️ | 내장 ✅ | 내장 ✅ |
| Provider 필요 | 불필요 ✅ | 필요 ⚠️ | 선택적 ✅ |
| DevTools | Redux DevTools ✅ | 전용 ✅ | 제한적 ⚠️ |
| TypeScript | 우수 ✅ | 우수 ✅ | 최고 ✅✅ |
| React 외부 사용 | 가능 ✅ | 불가 ❌ | 불가 ❌ |