Jotai 적용에 앞서 Jotai 알아보기
0. Jotai는 조타이가 아니라 일본어로 상태를 의미하는 状態(죠-타이)
영문 표기로는 Jotai이지만 실제 영어로 표기해야하는 발음은 Jaw-tai 가 맞겠다.
1. Jotai는 어떠한 상태관리 라이브러리인가요
- jotai는 useState + useContext를 개선한 recoil과 비교되는 라이브러리라 생각하면 된다.
- jotai는 내부적으로 context API를 이용하면서도 context의 불필요한 리렌더링을 방지한다.
- 리덕스에 비해 보일러 플레이트 코드가 적고 사용이 간편하다.
- atomic 한 구조에 잘 어울린다. bottom-up 상태관리를 한다. (부모에서 atom 정의하고, 자식이 받아온 atom을 set)
- 번들 크기가 작다. 매우 가볍다.
- useAtom에서 사용되는 atom은 가비지 컬렉터의 대상이 되어 atom이 더이상 사용되지 않으면 메모리에서 제거된다.
- 사용자가 많아지는 추세다
2. 많이 사용하는 Redux를 안 쓰고 Jotai를 사용하는 이점은 무엇인가
Redux는
Redux를 도입하는 것은 프로젝트별로 다른 세팅을 요구하고, 사용 경험이 없는 사람은 복잡한 액션과 리듀서를 이해하는 것이 쉽지 않다.
또한, state에 의존하지 않고 리듀서를 남발하게 되면 무분별하게 수많은 컴포넌트가 리렌더링 되어 버리기 쉽다.
그만큼 복잡하게 상태를 설계하지 않으면 되는 일이라고 생각하지만, 개발 중에는 중간이란건 없다.
우선 어찌됐든 구현을 해야하니까.
Jotai는
Recoil은 Javascript로, Jotai는 TypeScript로 작성되었다.
useState + useContext 와 사용법이 유사하여 간단하고 직관적인 상태관리가 가능하다.
컴포넌트 구조를 atomic하게 만들었을 때 리렌더링 되는 컴포넌트를 최소화할 수 있다.
Context보다
더 간결하고 효율적인 상태 관리를 제공한다.
jotai는 개발에 유용한 util을 제공한다.
하나의 예시,
localStroage나 sessionStorage에서 atom으로 상태관리를 할 수 있도록 돕는다.
import { useAtom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
const darkModeAtom = atomWithStorage('darkMode', false)
const Page = () => {
const [darkMode, setDarkMode] = useAtom(darkModeAtom)
return (
<>
<h1>Welcome to {darkMode ? 'dark' : 'light'} mode!</h1>
<button onClick={() => setDarkMode(!darkMode)}>toggle theme</button>
</>
)
}
darkMode를 atomWithStorage를 이용하여 구현한 샘플 코드
3. Jotai 세팅 방법
설치만 해주면 끝이다. 별도의 세팅은 필요없다.
npm i jotai
4. Jotai 사용 방법
1) atom, useAtom: state생성하고 사용하기
import { atom } from 'jotai';
const counter = atom(0);
const [count, setCounter] = useAtom(counter);
const onClick = () => setCounter(prev => prev + 1);
- useState 처럼 사용된다.
- atom엔 작고 간단한 데이터가 들어가는게 좋다
- atom은 전역적으로 접근 가능한 상태가 된다
2) atomWithStorage: localStorage로 상태 유지시키기
import { useAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
const theme = atomWithStorage('dark', false);
const [appTheme, setAppTheme] = useAtom(theme);
const handleClick = () => setAppTheme(!appTheme);
브라우저를 재방문 할 때도 상태를 유지시키고 싶을 때 localStorage를 이용한다.
이 함수는 atom의 상태와 localStorage에 저장된 값을 자동으로 동기화해주는 기능을 한다.
3) Read-only atom : 부모 atom 값을 변경하지 않고 atom 파생
import { atom, useAtom } from 'jotai';
const textAtom = atom('readonly atoms')
const uppercase = atom((get) => get(textAtom).toUpperCase())
const [lowercaseText, setLowercaseText] = useAtom(textAtom);
const [uppercaseText] = useAtom(uppercase);
const handleChange = (e) => setLowercaseText(e.target.value);
atom의 값을 직접적으로 바꾸지 않고 다른 atom의 값을 읽어, 변환시킨 값을 사용해 뷰를 조작하기 쉬워진다.
const friendsStatus = atom([
{ name: "John", online: true },
{ name: "David", online: false },
{ name: "Micheal", online: true }
]);
const onlineFriends = atom((get) => get(friendsStatus).filter((item) => item.online));
const offlineFriends = atom((get) => get(friendsStatus).filter((item) => !item.online));
onlineFriends와 offlineFriends는 콜백 함수로 friendsStatus의 값을 읽어 새로운 값을 반환한다.
원본 데이터(friendsStatus)의 online 상태에 따라
onlineFriends는 online: true를 필터링한 배열을 반환하고
offlineFriends는 online: false를 필터링한 배열을 반환하고 있다.
online ? onlineFriends : offlineFriends 와 같은 형태로 파생된 값을 이용하는 방식으로 뷰를 조작한다.
4) Provider: atom을 전역적으로 선언하지 않고 상태 값을 하위 트리에만 제공할 수 있다.
const Component = () => (
<Provider
initialValues={[
[stringAtom, 'hello'],
[numberAtom, 123],
]}
>
<Child />
</Provider>
)
- 여러 하위 트리에 서로 다른 상태 값을 제공하거나 중첩해서 사용할 수 있다.
const Component = () => (
<div>
<Provider>
<FirstChild />
</Provider>
<Provider>
<SecondChild />
</Provider>
</div>
)
- 컴포넌트가 다시 마운트 되었을 때 모든 atom의 값을 초기화하기 위해 사용할 수 있다.
- store props를 이용해 Provider 하위 트리에 베타적인 store를 제공할 수 있다.
const myStore = createStore()
const Root = () => (
<Provider store={myStore}>
<App />
</Provider>
)
- useStore를 이용해 하위 트리에서 상태 값을 읽거나 수정할 수 있다.
const Component = () => {
const store = useStore()
// ...
}
5) store 생성 방법
const myStore = createStore()
const countAtom = atom(0)
myStore.set(countAtom, 1)
const unsub = myStore.sub(countAtom, () => {
console.log('countAtom value is changed to', myStore.get(countAtom))
})
// unsub() to unsubscribe
const Root = () => (
<Provider store={myStore}>
<App />
</Provider>
)
전역 스토어 이용 방법
const defaultStore = getDefaultStore()
useUpdateAtom, useAtomValue: 읽기나 쓰기 중 하나만 필요할 때 사용
값을 업데이트하거나 조회만 할 때 사용할 수 있습니다.
useUpdateAtom 대신 useAtom을 사용할 때
const countAtom = atom(0);
const [, setCount] = useAtom(countAtom);
setCount(1)
useUpdateAtom을 사용할 때
const countAtom = atom(0);
const setCount = useUpdateAtom(countAtom);
setCount(1)
atomWithReset, useResetAtom: reset이 필요한 atom을 정의하고 리셋
atomWithReset으로 reset이 필요한 아톰을 정의하고,
useResetAtom으로 정의한 atom 값 초기화할 수 있다.
const reset = useResetAtom(atom)
selectAtom: atom 객체의 특정 값 가져오기
const user = {
name: "young",
age: 10
}
const userAtom = atom(user);
const nameAtom = selectAtom(userAtom, (user) => user.name;
const ageAtom = selectAtom(userAtom, (user) => user.age;
nameAtom에만 의존하는 컴포넌트는 userAtom의 name값에 변경이 일어났을 때만 리렌더링 된다.
참고 자료
https://liebe97.tistory.com/49
https://programming119.tistory.com/263