React deep dive -11_Jotai

조용환·2024년 3월 11일
0

React_Deep_Dive

목록 보기
11/12

Jotai

React의 atom 모델에 영감을 받아 만들어진 상태 관리 라이브러리. 상향식(bottom-up) 접근법을 취하고 있음. 리덕스와 같이 하나의 큰 상태를 app에 내려주는 방식이 아니라, 작은 단위의 상태를 위로 전파할 수 있는 구조를 취하고 있음을 의미.

리액트 Context의 문제점인 불필요한 리렌더링이 일어난다는 문제를 해결하고자 설계돼 있으며, 추가적으로 개발자들이 메모이제이션이나 최적화를 거치지 않아도 리렌더링이 발생되지 않도록 설계
(책은 2022년 9월 기준 최신 버전인 1.8.3을 기준으로 설명)

atom

최소 상태를 의미. 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에서 찾아볼 수 있다.

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
}
  • 여기서 눈여겨 봐야할 것은 usereducer로 반환하는 상태값은 3가지 (version, valueFromReducer, atomFromReducer)이다.
  • 첫 번째는 store의 버전, 두 번째는 atom에서 get을 수행했을 때 반환되는 값, 세 번째는 atom 그 자체를 의미한다. Recoil과는 다르게, 컴포넌트 루트 레벨에서 Context가 존재하지 않아도 된다.
  • 자바스크립트에서 객체만을 키로 가지는 WeakMap을 사용해 store에 atom 객체 그 자체를 키로 활용해 값을 저장한다.
  • rerenderIfChanged를 통해 최신 값으로 업데이트 해준다.

useAtom

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에 영감을 받았기에 유사하면서 한계점을 극복하기 위한 노력이 보인다.

  • Recoil의 atom 개념을 도입하면서도 API가 간결하다.
  • Recoil에서는 atom에서 파생된 값을 만들기 위해서 seletor가 필요했지만, Jotai에서는 selector가 없이도 atom만으로 atom 값에서 또 다른 파생된 상태를 만들 수 있어 간결하다.
  • Jotai 자체도 타입스크립트로 작성돼 있어 타입 지원이 잘 되어 있으며, Flow로 작성되어 별도로 d.ts를 제공하는 Recoil 대비 장점으로 볼 수 있다.
  • 리액트 18의 변경된 API 지원하며, 현재 v2.x 버전가지 정식으로 출시돼있어 실제 서비스하는 app에도 무리 없이 사용 가능하다.
profile
practice react, javascript

0개의 댓글