상태 관리, 뭣이 중헌디

jh_leitmotif·2022년 4월 8일
2
post-thumbnail

전역에는 선언하지 말랬잖아!

학부 시절 귀에 딱지나도록 지겹게 들었던 이야기.

왠만하면 변수는 Global에 선언하지 말자, 는 이야기가 있다.

쉬운 예로 들어보자, 왜 var를 사용하지 않는가? 에 대한 것도 큰 관련이 된다.

아무튼, 그런 상태를 관리하기 위해 React에서 사용하는 상태 관리 도구들이 많다.

지금까지 Redux, Context API, Recoil, Zustand를 써보면서 나는 리액트 개발자로서,어떻게 상태를 관리해야될까? 하는 고민을 계속 생각하고 있다.

Redux?

뭔들 안그렇겠냐마는 그 중에서 Redux는 특히나 나에게 빅 엿을 선사했다.

지금도 그냥 쓸 줄 알지 자유자재로 이해하면서 쓴다고 느끼지 않고 있다.

신나게 state, effect만 쓰다가 reducer, dispatch라는 개념을 보면 'WTF?' 소리가 절로 나온다.

dispatch는 '보내다' 라는 뜻을 가지고 있어서 직관적이기는 한데... 많이 쓰는 단어도 아니고. 특히 reducer는 뜻과 기능을 매치할래야 매치하기 힘들다.

게다가 action, reducer 폴더를 따로 만들어야하고 추가되는 코드가 많다.

여기에 만약 store가 여러개라면 Store라는 폴더가 하나 더 추가될테고

듀얼 모니터가 뭐야, 트리플 모니터는 있어야 살펴보는 데에 눈의 피로감도 덜할 것 같다.

Context API..?

React를 위해 만들어진 Context는 좀 낫다.

'전역'의 컨셉을 가진 느낌인 Redux와는 약간 다르게 저장소의 기준점을 개발자가 따로 정의하는 듯한 기분도 들고.

또 useContext 훅이 있어서 쉽게 필요한 state를 땡겨올 수 있다.

대신 렌더링에 대한 부담을 항상 생각하게 한다.

Context.Provider 객체가 상태변화를 모니터링하는데, 만약 한 Context에 여러 컴포넌트들이 물려있다면 아예 상관없는 state의 변화로 인해 불필요한 리렌더링이 발생할 수 있다.

게다가 가독성에 대한 부분도 고민된다. Promise Chaining처럼 마냥 Context를 덮다보면

<AProvider>
 <BProvider>
   <CProvider>
   ....

위와 같은 괴상한 구조가 나올 수 있다.

따로 ReactNode를 prop으로 받는 Context로 분리하면 겉으로 보기엔 깔꼼하겠으나, DevTool로 컴포넌트 구조를 보면 뭔가 한없이 드릴링되는 구조가 나타난다.

스스로 느끼기엔 위 사진과 같은 느낌이라고 해야하나, 솔직히 맘에 딱 맞지는 않다.

Recoil

Recoil은 Context기반이지만, 조금 다르다.

어떠한 상태가 변하면, 정말로 그 상태와 연관된 컴포넌트들만 따로 리렌더링된다고 한다.
또한 컴포넌트가 해제되면 알아서 저장된 state를 초기화해준다. 여기에 따로 추가할 코드는 전혀 없다.

무엇보다도 쓰기 정말 쉽다.

개발자가 해야하는 것? 그건 그저 사용할 상태를 'atom'으로 선언해서 어떠한 저장소에다가 던져놓기만 하면 된다.

export const helloState = atom({
	key:'helloState',
    default:'hello?'
})

export const helloStateSelect = selector({
	key:'helloStateSelect',
    get: ({get}) =>{
    	return `${get(helloState)} 하세요!`

const [atomHelloState,setHelloState] = useRecoilState(helloState)
const helloSentence = useRecoilValue(helloStateSelect)

그리고 Recoil로 관리될 state를 사용할 컴포넌트들을 <RecoilRoot/> 로 감싸주기만 하면 된다. 보통은 App.js(또는 Router.js)에 둔다.

로그인 상태를 어떻게 제어할지에 대한 고민을 하다가 접하게 되었는데, 대충 이해하는 데에 5분, 써보는 데 5분 걸릴 정도로 진입 장벽이 낮았다.

더불어 Recoil에서 쓰는 hook은 직관적인데다 기존의 useState와 닮아서 쓰기 편하다.

selector의 경우 api call을 캐싱하므로 동일한 응답에 대해 추가적인 호출을 하지 않는다. 이는 성능에 굉장한 도움을 준다.

<RecoilRoot>
	<Parent>
    	<RecoilRoot>
        	<Chilren>

이런 구조의 Nesting도 가능하다.

이 경우엔, Parent에서 관리되는 state와 Children에서 관리되는 state는 독립적으로 구분된다.

기본적으로 override속성이 true로 설정되어 위에 언급한 작용이 가능하다.

false일 때는 해당 RecoilRoot는 자식 컴포넌트의 렌더링만 맡게 되어 가까운 Root의 State를 참조한다고 하는데, 이 부분은 확실하지 않다.

개인적으로 쓸 때는 단 한 개의 RecoilRoot만 사용했었고, 즉 모든 컴포넌트가 state에 접근 가능한 상태였다.

여기에 피드백 받은 부분은 과연 그것이 옳은 구조인가? 에 대한 부분이고, 만약 Recoil에서만 state를 관리하게 된다면 분명 언젠가 유지보수에 대한 문제가 있을거라는 생각도 든다.

Zustand

Zustand는 2021년에 나온, 아마 1년도 안된 걸로 알고 있는 따끈따끈한 라이브러리다.

Context를 기반으로 두지 않기에 리렌더에 대한 걱정이 없다.

Redux를 쉽게 써보자! 라는 느낌이라고 해야할까?

냉장고에다가 간식 꿍쳐두고, 먹고 싶을 때 꺼내서 먹는 기분이다.
오직 '필요'에 의해서 Store를 호출하고, 거기서 필요한 값을 꺼내 쓴다.

동작을 분리하고자 커스텀 훅을 쓰는 듯한 방식이라 친근하기도 하고.

당장 전역 관리 어떤 걸로 쓸래? 라고 누군가 물어보면 Zustand 쓸거야!! 라고 대답할 것 같다.

다만 아직 많이 사용해본 상태는 아니라, 조금 더 지켜보아야할 것 같기도.

참고해볼만한 링크들

https://intrepidgeeks.com/tutorial/zustad-state-management-library
https://ui.toast.com/weekly-pick/ko_20210812
https://ridicorp.com/story/how-to-use-redux-in-ridi/

이 링크들 너무나 맛있다.

profile
Define the undefined.

0개의 댓글