이번 리팩토링을 하며 Context API 기반의 코드에서 Jotai 로 바꾸게 되면서 Jotai 에 대한 개념을 보다 잘 이해하기 위해 발표 주제로 선정하게 되었어요.
이번 포스트에선 Jotai 와 Jotai 공식 문서에 Core 로 분류되는 네가지 개념에 대해 소개하려 해요.
Jotai 목차
- Atom
- useAtom
- Store
- Provider
Jotai, 상태 관리를 위해 쓰는 라이브러리예요. 일본어로 '상태' 라는 뜻을 가지고 있어요.

Atom 은 Jotai 에서 전역 상태를 만드는 가장 기본적인 함수예요.
import { atom } from 'jotai';
const messageAtom = atom('hello');
atom 함수는 atom config 를 생성해요. atom config 는 값이 아니라 불변성 객체예요.atom 을 만들 때, 초기 값을 제공해줄 수 있어요.atom 함수를 사용해 읽기, 쓰기, 읽기-쓰기 전용의 파생아톰을 만들 수 있어요.위 아톰은 읽기와 쓰기가 모두 되는 아톰이에요.
여기서 아톰의 콜백함수를 통해 읽기 전용, 쓰기 전용 아톰을 만들아 Redux 의 actions 처럼 특정 동작만 허용되는 용도로 사용할 수 있어요.
const messageAtom = atom('hello');
const readOnlyAtom = atom((get) => get(messageAtom));
get 메서드는 아톰의 값을 가져올 수 있어요.const messageAtom = atom('hello');
const writeOnlyAtom = atom(null, (get, set, update) => {
set(messageAtom, update);
})
null 값을 넣어줘요.get 은 아톰의 값을 읽어올 수 있지만 의존성이 추적되지 않아요.set 은 아톰의 값을 새로 쓸 수 있어요. update 는 새로 넣어줄 아톰의 값을 의미해요.// 읽기-쓰기 전용 아톰
const readWriteAtom = atom((get) => get(message),
(get, set, update) => {
set(messageAtom, update);
}
)
useMemo 와 useRef 등을 사용하는 것이 좋아요.위에서 만든 아톰과 파생아톰들은useAtom 을 통해 사용할 수 있어요.
const someAtom = atom('??');
const [value, setValue] = useAtom(someAtom);
React.useState 처럼 useAtom 을 사용해 첫번째 값은 읽기 전용 값을, 두번째론 값을 변경시킬 수 있는 메서드를 받아올 수 있어요.
읽기, 쓰기 전용 아톰은 다음과 같이 사용할 수 있어요.
const value = useAtom(readAtom);
const setValue = useAtom(writeAtom);
아톰의 읽기, 쓰기 중 둘 중 하나만 하고 싶다면 useAtomValue, useSetAtom 을 사용할 수 있어요.
const value = useAtomValue(readWriteAtom);
const setValue = useSetAtom(readWriteAtom);
이렇게 명확하게 가져오는 것은 [, setValue] = useAtom(atom) 을 사용했을 때보다 렌더링 성능을 높여준다고 해요.
createStore 메서드는 새로운 스토어를 만들어요. 이렇게 만들어진 스토어는 Provider 내부로 전달될 수 있어요.
const myStore = createStore();
const Root = () => {
<Provider store={myStore} />
<App />
</Provider>
}
❓ 스토어를 왜 나누나요?
스토어를 나누지 않으면 하나의 Root 스토어만 존재해요.
해당 Root 스토어에 모든 컴포넌트에서 자유롭게 접근할 수 있다면, 예상치 못한 곳에서 상태가 바뀔 수도 있어요.
따라서 store 를 나누어 각 컴포넌트에서만 사용되는 아톰들을 안전하게 관리할 수 있어요.
스토어는 get, set, sub 의 세가지 메서드를 가질 수 있어요.
getsetsubconst myStore = createStore();
const someAtom = atom(0);
myStore.set(someAtom, 1);
// sub 메서드로 만들어진 unsub은 구독을 취소할 수 있어요
const unsub = myScore.sub(someAtom, () => {
console.log('Atom 이 변경되었어요!');
})
getDefaultStore 는 Provider 가 없는 경우 기본 스토어를 반환해주는 함수예요.
Provider 컴포넌트는 서브 트리 컴포넌트를 위한 상태를 제공해요. React 의 Context 처럼 사용된다고 생각하면 정답 🙆♀️
같은 atom 을 사용하더라도 참조하는 Provider 가 다른 경우, 다른 값을 가지게 돼요.
import { Provider } from 'jotai';
export const someAtom = useAtom(1);
const Root = () => {
return (
<Provider>
<Child1 />
</Provider>
<Provider>
<Child2 />
</Provider>
)
}
<Child1 /> 컴포넌트와 <Child2 /> 컴포넌트는 서로 다른 아톰의 값을 가지게 돼요.
아톰의 값을 각각 다르게 가지는 Provider 의 특성과useHydrationAtoms 을 사용하여 아톰의 초기값을 설정할 수 있어요.
<Provider>
<AtomHydrator initialValues={[[someAtom, 1]]}>
<Component />
</AtomHydrator>
</Provider>
// AtomHydrator.js
import { useHydrateAtoms } from 'jotai';
const AtomHydrator = ({initalValues, children}) => {
useHydrateAtoms(new Map(atomValues));
return chilren;
}
⚠️ 타입스크립트에선
useHydrateAtoms가 일종의 오버로드 함수이지만, 타입스크립트는 오버로드된 함수의 타입을 추측할 수 없기 때문에Map을 사용해 초기값을 전달하는 것이 권장돼요
마찬가지로 Provider 와 atomWithReset, useResetAtom 으로 아톰을 초기화시켜줄 수 있어요.
atomWithReset 으로 Resettable 한 아톰을 만들고, useResetAtom 훅을 사용해서 초기 값으로 되돌려줄 수 있어요.
import { atomWithReset } from 'jotal/utils';
export const dollarsAtom = atomWithReset(0);
const Root = () => {
<Provider>
<App />
</Provider>
}
// App.js
import { useResetAtom } from 'jotai/utils';
const App = () => {
const resetDollars = useResetAtom(dollarsAtom);
useEffect(() => {
resetDollars();
}, []);
}
<Provider store={MyStore}>
<MyComponent />
</Provider>
<YourComponent />// /stores.js
export const atom1 = useAtom(0);
export const atom2 = useAtom(1)store.set() 을 사용할 수 있어요. const myStore = createStore();
const atom1 = useAtom(0);
const atom2 = useAtom(1);
myStore.set(atom1);
myStore.set(atom2);
export default myStore;
원래 utils 함수 설명하면서 다루려고 했는데, 잘 사용하지 않을 것 같아서 질문으로 대체할게요!
import { atom, useAtom } from 'jotai';
import { useHydrateAtoms } from 'jotai/utils';
const countAtom = atom(0);
const CounterPage = ({ countFromServer }) => {
useHydrateAtoms([[countAtom, countFromServer]]);
const [count] = useAtom(countAtom);
// `countFromSever` 의 값으로 바뀐다
}
values: Iterable<readonly [Atom<unknown>, unkonwn]> [atom, value] 로 이뤄져있는 iterable 한 튜플을 받아요.options? 특정 스토어를 지정하거나, 강제 redydrate 를 할수 있는 옵션이에요.