
react나 next.js로 프로젝트를 할 때, 항상 빠지지 않는 것이 있다.
바로 "상태 관리 라이브러리"
만약 상태 관리 라이브러리를 사용하지 않고 순수하게 한다고 했을 때는
부모가 자식에게 내려주는 props를 개념을 이용하여 사용할 것이다.
하지만 부모에서 부모의 자식의 자식의 자식의... 구조거나 관리해야 하는 상태가 많다면 매우 복잡해질 수 있다.
(props drilling이 대표적인 문제 중 하나이다.)
그래서 등장한 것이 바로 상태 관리 라이브러리들이다.
상태 관리 라이브러리는 여러가지가 존재한다. 대표적으로 context API, redux, recoil, zustand 등등이 있다.
그 중에서 나는 이번 개인 프로젝트에 적용한 jotai에 대해 설명할려고 한다.
Jotai란?
Jotai는 앞서 말했다시피 상태 관리 라이브러리 중 하나이다.
일본어로 "状態" 상태를 뜻한다. (직관적인 뜻이 마음에 든다.) 그리고 react-spring을 개발한 Pmndrs 팀에서 만든 것이다.
Jotai의 특징
원자 기반 상태관리
Jotai는 atom이라는 것을 사용한다.
atom... 학창시절 우리가 배웠던 그 원자가 맞다.
Jotai는 이러한 느낌으로 쪼개서 관리를 한다.
간단하고 가벼움
또한 Jotai는 사용법을 보면 알 수 있듯이, atom으로 정의하고 useAtom으로 사용하는 매우 쉽고 간단하게 사용할 수 있다.
Jotai의 사용법
npm install jotai
yarn add jotai
import { atom, useAtom } from 'jotai';
const countAtom = atom(0)
function BasicStep() {
const [count, setCount] = useAtom(countAtom);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>플러스</button>
</div>
);
}
여기서 atom안에 초기값을 넣어서 상태를 만든다.
그리고 useAtom(atom)을 이용해서 atom을 읽거나 업데이트 하도록 한다.
const countAtom = atom(10);
const doubleAtom = atom((get) => get(countAtom) * 2);
const [doublescore] = useAtom(doubleAtom);
console.log(doublescore); // 20
여기서 get()은 다른 atom값을 읽을 때 사용한다.
그리고 코드안의 doubleAtom은 읽기 전용이다. (즉, set 불가)
여기서 get(countAtom)으로 새로운 형태로 만들 수 있다.
const countAtom = atom(10);
const plusAtom = atom(
null, // 읽기 없음
(get, set) => set(countAtom, get(countAtom) + 1)
);
첫번째 인자는 읽기이지만 null을 넣었으므로 여기에는 읽기 기능이 없다.
두번째 인자를 보면 (get, set)이 보이는데 이건 다른 atom을 읽고 수정이 가능하다.
const [, plus] = useAtom(incrementAtom);
plus(); // count + 1
const count = atom(0);
const double = atom(
(get) => get(count) * 2,
(get, set, newValue) => set(count, newValue / 2)
);
읽을 때는 두배값을 반환하고 쓸때는 새로운 값의 절반을 count에 저장한다.
const fetchAtom = atom(async() => {
const res = await fetch("...");
const data = await res.json();
});
function User() {
const [user] = useAtom(fetchAtom);
return <div>{user.name}</div>;
};
여기서 주의할 점은 suspense로 감싸줘야 한다.
<Suspense fallback={<p>로딩중</p>}>
<User />
</Suspense>
다른 atom들을 가지고 파생 상태를 만들어 줄 수 있다.
const one = atom(1);
const anotherone = atom(2);
const total = atom((get) => get(one) * get(anotherone));
여기서 one 또는 anthorone의 값이 바뀌면 total이 자동으로 갱신된다.
Provider는 atom들이 공유되는 범위를 결정한다.
예를 들면 layout.tsx에 <Provider></Provider>로 감싼다면 그 안에 있는 것은 서로 공유가 된다.
그렇다면 아예 Provider를 안쓰면 어떻게 될까?
놀랍게도 기본 Provider이 자동 생성되어 작동이 된다.
(그 대신 내가 직접 제어할 수 없는 단점이 존재한다.)
그럼 또 다른 생각이 들것이다.
"나는 이 하나의 값을 독립적으로 관리하고 싶다."
그렇다면 개별적으로 provider을 만들어주면 된다.
쉽게 예를 들자면 const thisIsBool = atom(true);가 있다고 가정해보자.
만약 provider를 사용하고 있지않거나 공통으로 provider로 묶었다면 어느 컴포넌트 안에 있는 atom 상태가 변경되면 동일한 atom을 사용하고 있는 곳도 같이 변경된다.
이걸 막기 위해서는 하나로 묶지말고 개별적으로 묶어주면 된다.
import { Provider, createStore, atom, useAtom } from 'jotai';
const booleanJotai = atom(false);
const store1 = createStore():
const store2 = createStore();
function Bool() {
const [isBoolean, setIsBoolean] = useAtom(booleanJotai);
return (
<div>
<p>{isBoolean}</p>
</div>
)
};
export default function App() {
return (
<div>
<Provider store={store1} >
<Bool />
</Provider>
<Provider store={store2} >
<Bool />
</Provider>
</div>
)};