React의 atom 모델에 영감을 받아 만들어진 상태 관리 라이브러리. 상향식(bottom-up) 접근법을 취하고 있음. 리덕스와 같이 하나의 큰 상태를 app에 내려주는 방식이 아니라, 작은 단위의 상태를 위로 전파할 수 있는 구조를 취하고 있음을 의미.
리액트 Context의 문제점인 불필요한 리렌더링이 일어난다는 문제를 해결하고자 설계돼 있으며, 추가적으로 개발자들이 메모이제이션이나 최적화를 거치지 않아도 리렌더링이 발생되지 않도록 설계
(책은 2022년 9월 기준 최신 버전인 1.8.3을 기준으로 설명)
최소 상태를 의미. atom 하나로 파생된 상태까지 만들 수 있다는 점에서 차이가 있음
const counterAtom = atom(0)
// 담기는 정보 예시
console.log(counterAtom)
// ...
// {
// init 0,
// read: (get) => get(config),
// write: (get, set, update) =>
// set(config, typeof update === 'function' ? update(get(config)) : update)
// }
고유 키가 필요했던 Recoil과 다르게 Jotai는 별도 key가 필요없다.
상태는 useAtomValue에서 찾아볼 수 있다.
다음은 Jotai의 useAtomValue 구현이다.
export function useAtomValue<Value>(
atom: Atom<Value>,
scope?: Scope,
): Awaited<Value> {
const ScopeConteext = getScopeContext(scope)
const scopeContainer = useContext(ScopeContext)
const { s: store, v: versionFromProvider } = scopeContainer
const getAtomValue = (version?: VersionObject) => {
const atomState = store[READ_ATOM](atom, version)
// ...
}
const [[version, valueFromReducer, atomFromReducer], rerenderIfChanged] = ((prev, nextVersion) => {
const nextValue = getAtomValue(nextVersion)
if (Object.is(prev[1], nextValue) && prev[2] === atom) {
return prev //bail out
}
return [nextVersion, nextValue, atom]
}, versionFromProvider,
(initialVersion) => {
const initialValue = getAtomValue(initialVersion)
return [initialVersion, initialValue, atom]
})
let value =valueFromReducer
if (atomFromReducer !== atom) {
rerenderIfChanged(version)
value = getAtomValue(version)
}
useEffect(() => {
const { v: versionFromProvider = scopeContainer}
if (versionFromProvider) {
store[COMMIT_ATOM](atom, versionFromProvider)
}
const unsubscribe = store[SUBSCRIBE_ATOM](
atom,
rerenderIfChanged,
versionFromProvider,
)
rerenderIfChanged(versionFromProvider)
return unsubscribe
}, [store, atom, scopeContainer])
//...
return value
}
useState와 동일한 형태의 배열을 반환한다.
첫 번째로는 atom의 현재 값을 나타내는 useAtomValue 훅의 결과를 반환하며, 두 번째로는 useSetAtom 훅을 반환하는데, 이 훅은 atom을 수정할 수 있는 기능을 제공한다.
다음 코드는 Jotai에서 간단한 상태를 선언하고, 만들어진 상태로부터 파생된 상태를 사용하는 예제다.
import { atom, useAtom, useAtomValue } from 'Jotai'
const counterState =atom(0)
function Counter() {
const [, setCount] = useAtom(counterState)
function handleButtonClick() {
setCount((count) => count +1 )
}
return (
<>
<button onClick={handleButtonClick}>+</button>
</>
)
}
const biggerThan10 = useAtomValue(isBiggerThan10)
return (
<>
<h3>{count}</h3>
<p>count is bigger than 10: {JSON.stringify(biggerThan10)}</p>
</>
)
}
export default function App() {
return (
<>
<Counter />
<Count />
</>
)
}
Recoil에 영감을 받았기에 유사하면서 한계점을 극복하기 위한 노력이 보인다.