모던 리액트 Deep Dive - 상태관리

2025. 12. 9. 22:08·Study/모던 리액트 Deep Dive

⚡상태관리의 필요성

상태란 흔히 웹 애플리케이션을 개발할때 이야기하는 상태는 어떠한 의미를 지닌 값이며, 애플리케이션의 시나리오에 따라 지속적으로 변경될 수 있는 값을 의미함 (UI, URL, form, 서버에서 가져온 값)

5.1.1 리액트 상태관리의 역사

🌕Flux 패턴 

  • 단방향으로 데이터 흐름을 변경하는 것 (양방향 데이터 바인딩 / 코드의 양이 많아지고 변경시나리오가 복잡

💡 Flux패턴의 용어

  • action : 어떠한 작업을 처리할 액션과 그 액션이 발생시 함께 포함시킬 데이터를 의미
  • dispatcher : 액션을 스토어로 보내는 역할
  • store : 실제 상태에 따른 값과 상태를 변경할 수 있는 메서드를 가짐
  • view :  리액트의 컴포넌트에 해당하는 부분

양방향 데이터 바인딩은 데이터 흐름은 모두 액션이라는 한 방향으로 줄어드므로 데이터의 흐름을 추적하기 쉽고 코드를 이해하기가 수월해짐

🌕리덕스의 등장

  • 리덕스는 대표적인 Flux 패턴을 사용하는 상태관리 라이브러리이고 Elm 아키텍처를 도입
  • 리덕스는 하나의 상태 객체를 스토어에 저장해 두고, 이 객체를 업데이트하는 작업을 디스패치해 업데이트를 수행함

🌕Context API와 useContext

🌕훅의 탄생 그리고 React Query와 SWR

  • 두 라이브러리 모두 외부에서 데이터를 불러오는 fetch를 관리하는 데 특화된 라이브러리지만, API 호출에 대한 상태를 관리하기 때문에 HTTP 요청에 특화된 상태 관리 라이브러리임

🌕 Recoil, Zustand, Jotai, Vlatio에 이르기까지

  • 16.8 이상의 버전을 요구하고 있고 작은 크기의 상태를 효율적으로 관리하기 좋은 라이브러리

5.2.1 가장 기본적인 방법 : useState와 useReducer

  • useState의 등장으로 리액트에서는 여러 컴포넌트에 걸쳐 손쉽게 동일한 인터페이스의상태를 생성하고 관리할 수 있게 됨
  • useCounter라는 훅을 만들어서 함수형 컴포넌트 어디서든 사용할 수 있게 구현가능
function useCounter(init: number = 0) {
	const[counter, setCounter] = useState(init)
    
    function inc() {
    	setCounter((prev) => prev + 1)
    }
    
    return {counter, inc}
}

 

  • 리액트의 훅을 기반으로 만든 사용자 정의 훅은 함수 컴포넌트에서 재사용 가능하다는 장점
  • 실제 useReducer를 타입스트립트로 작성하려면 다양한 오버로딩이 필요함
  • useState나 useReducer 모두 약간의 구현 상의 차이만 있을 뿐, 두 훅 모두 지역 상태 관리를 위해 만들어짐

5.2.2 지역상태의 한계를 벗어나보자 : useState의 상태를 바깥으로 분리하기

  • useState의 한계는 리액트가 만든 클로저 내부에서 관리되어 지역 상태로 생성되기 때문에 해당 컴포넌트에서만 사용할 수 있다는 것
  • useState로 컴포넌트의 리렌더링을 실행하여 최신값을 가져오는 방법은 해당 컴포넌트에서만 유효한 전략임 즉, 반대쪽의 다른 컴포넌트에서는 상태 변화에 따른 리렌더링을 일으킬 무언가가 없기 때문에 렌더링되지 않기 때문

💡함수외부에서 상태를 참조하고 렌더링까지 자연스럽게 일어나려면 다음과 같은 조건을 만족해야 함!

1. 컴포넌트 외부 어딘가에 상태를 두고 여러 컴포넌트가 같이 쓸 수 있어야 함

2. 외부에 있는 상태를 사용하는 컴포넌트는 상태의 변화를 알아챌 수 있어야 하고, 상태가 변할 때마다 리렌더링이 일어나서 컴포넌트를 최신 상태값 기준으로 렌더링해야 함. 상태 감지는 상태를 변경시키는 컴포넌트뿐 아니라 상태를 참조하는 모든 컴포넌트에서 동일하게 작동해야 함

3. 상태가 윈시 값이 아닌 객체인 경우, 객체에 내가 감지하지 않는 값이 변한다 하더라도 리렌더링이 발생해서는 안됨

이 3가지를 만족시킬 수 있는 것이 store임


5.2.3 useState와 Context를 동시에 사용해보기 

  • useState / useStoreSelector훅의 단점은 스토어를 사용하는 구조는 반드시 하나의 스토어만 가지게 된다는 것
  • 하나의 스토어를 가지면 해당 스토어는 마치 전역 변수처럼 작동하게 되어 동일한 형태의 여러 개의 스토어를 가질 수 없게 됨!

🌓만약, 훅을 사용하는 서로 다른 스코프에서 스토어의 구조는 동일하되, 여러 개의 서로 다은 데이터를 공유해 사용하고 싶다면 어떻게 해야 할까?

가장 떠오르는 방법은 createStore를 이용해 동일한 타입으로 스토어를 여러개 만드는 것이지만, 이 방법은 완벽하지도 않고, 번거롭기 때문에 이런 문제에서는 리액트의 Context를 사용하는 것이 좋음!

//createStore를 이용해 동일한 타입으로 스토어를 여러개 만드는 것
const store1 = createStore({ count : 0 })
const store2 = createStore({ count : 0 })
const store3 = createStore({ count : 0 })
....


//스토어와 context 함께 사용
export const CounterStoreContext = createContext<Store<CounterStore>>(
  createStore<CounterStore>({ count:0, text: 'Hello' })
);

export const CounterStoreProvider = ({ 
	initialState,children 
} : PropsWithChildren<{
    initialState : CounterStore
}>) => {
  const storeRef = useRef<Store<CounterStore>>()

  if (!storeRef.current) {
    storeRef.current = createStore(initialState)
  }
  
  return (
    <CounterStoreContext.Provider value={storeRef.current}>
      {children}
    </CounterStoreContext.Provider>
  )
);
  • storeRef를 사용하여 스토어를 제공하는데, 그 이유는 Provider가 넘기는 props가 불필요하게 변경돼서 리렌더링되는 것을 막기 위함이고, useRef를 사용했기 때문에 CounterStoreProvider는 최초 렌더링에서만 스토어를 만들어 값을 전달하게 될것임

🌚Context와 Provider를 기반으로 각 store 값을 격리해서 관리했을 때의 장점

  • 스토어를 사용하는 컴포넌트는 해당 상태가 어느 스토어에서 온 상태인지 신경 쓰지 않아도 됨
  • Context와 Provider를 관리하는 부모 컴포넌트의 입장에서 자신이 자식 컴포넌트에 따라 보여주고 싶은 데이터를 Context로 잘 격리하기만 하면 됨 (부모와 자식 컴포넌트의 책임과 역할을 이름이 아닌 명시적인 코드로 나눌 수 있어 코드 작성이 한결 용이해짐)

💡리액트에서 상태관리와 렌더링에 대해 넓은 시야를 가지게 될려면 코드를 작성할때 고려할 것 

  • useState, useReducer가 가지고 있는 한계, 컴포넌트 내부에서만 사용할 수 있는 지역 상태라는 점을 극복하기 위해 외부 어딘가에 상태를 둠
  • 외부의 상태변경을 각자의 방식으로 감지해 컴포넌트의 렌더링을 일으킴

5.2.4 상태 관리 라이브러리 Recoil, Jotai, Zustand 살펴보기

🌚 페이스북이 만든 상태 관리 라이브러리 Recoil

  • 훅의 개념으로 상태관리를 시작한 최초의 라이브러리 중 하나Recoil의 핵심 AP인 RecoilRoot, atom, useRecoilValue, useRecoilState를 살펴보고 Recoil에서는 상태 값을 어디에 어떻게 저장하는 지 알아보자

🌓 RecoilRoot

  • Recoil를 사용하기 위해 RecoilRoot를 애플리케이션의 최상단에 선언해야됨
  • Recoil에서 생성되는 상태값을 저장하기 위한 스토어를 생성하는 것을 확인 할 수 있음
  • 상태값은 RecoilRoot로 생성된 Context의 스토어에 저장됨
  • 스토어의 상태값에 접근할 수 있는 함수들이 있으며, 이 함수를 활용해 상태값에 접근하거나 상태값을 변경할 수 있음
  • 값의 변경이 발생하면 이를 참고하고 있는 하위 컴포넌트에 모두 알림

🌓 atom

  • 상태를 나타내는 Recoil의 최소 상태 단위
  • key 값을 필수로 가지며, 애플리케이션 내부에서 유일한 값이어야 함(atom과 selector를 만들때 주의를 요함)
  • defalut는 atom의 초깃값을 의미
  • atom의 값을 컴포넌트에서 읽어오고 이값의 변화에 따라 컴포넌트를 리렌더링하려면 useRecoilValue와 useRecoilState를 사용하면 됨 
🌕 useRecoilValue
atom의 값을 읽어오는 훅
🌕 useRecoilState
useState와 유사하게 값을 가져오고, 이 값을 변경할 수도 있는 훅
  • Recoil에서 한 가지 불확실한 점은 정식 버전인 1.0.0의 출시 시점이며, 0.x.x 버전은 다른 주 버전과 다르게 부 버전이 변경돼도 호환성이 깨지는 변경사항이 발생할 수 있는 초기 버전으로 간주되어 라이브러리르 사용할 때에 추가적인 주의가 필요함!

🌚 Recoil에서 영감을 받은, 그러나 조금 더 유연한 Jotai

  • Recoil의 atom 모델에 영감을 받아 만들어진 상태 관리 라이브러리
  • 리덕스와 같이 하나의 큰 상태를 애플리케이션에 내려주는 방식이 아니라, 작은 단위의 상태를 위로 전파할 수 있는 구조를 취하고 있음을 의미함
  • 리액트 Context의 문제점인 불필요한 리렌더링이 일어난다는 문제를 해결하고자 설계되어 있으며, 추가적으로 개발자들이 메모이제이션이나 최적화를 거치지 않아도 리렌더링이 발생되지 않도록 설계되어 있음
  •  Recoil의 atom 개념을 도입하면서도 API가 간결하다는 점

🌓 atom

  • Recoil과 동일하게 최소 상태 단위를 나타내지만, atom 하나로 파생된 상태까지 만들 수 있음
  • 고유한 key를 필요로 했던 Recoil과는 다르게 별도의 key를 내려주지 않아도 됨
  • Jotai에서 atom에 따로 상태를 저장하고 있지 않음 > userAtomValue에서 상태를 저장해둠

🌓 userAtomValue

  • useReducer에서 반환하는 상태값은 3가지: [version, valueFromReducer, atomFromReducer]
  • 첫 번째는 store 버전, 두 번째는 atom에서 get을 수행했을 때 반환되는 값, 세 번째는 atom 그 자체를 의미함
  • atom의 값은 store에 존재한다는 것을 알 수 있으며, store에 atom 객체 그 자체를 키로 활용해 값을 저장함
  • 이러한 방식을 위해 WeakMap이라고 하는, 자바스크립트에서 객체만을 키로 가질 수 있는 독특한 방식의 Map을 활용해 recoil과는 다르게 별도의 key를 받지 않아도 스토어에 값을 저장할 수 있음

💡rerenderIfChanged (리렌더링을 일으키기 위해 사용함)가 일어나는 경우

  • 첫째, 넘겨받은 atom이 Reducer를 통해 atom과 달라지는 경우
  • 둘째, ubscribe를 수행하고 있다가 어디선가 이 값이 달라지는 경우
  • 이러한 로직 덕분에 atom의 값이 어디서 변경되더라도 useAtomValue로 값을 사용하는 쪽에서는 언제든 최신 값의 atom을 사용해 렌더링할 수 있게 됨

🌓 useAtom

  • useState와 동일한 형태의 배열을 반환함
  • 첫 번째로는 atom의 현재 값을 나타내는 useAtomValue 훅의 결과를 반환하며, 두 번째로는 useSetAtom 훅을 반환함
  • 해당 훅은 atom을 수정할 수 있는 기능을 제공함

🌚작고 빠르며 확장에도 유연한 Zustand

  • Zustand는 리덕스에 영감을 받아 만들어짐
  • 즉, atom이라는 개념으로 최소 단위의 상태를 관리하는 것이 아니라 Zustand에서는 하나의 스토어를 중앙 집중형으로 활용해 이 스토어 내부에서 상태를 관리하고 있음
  • 따라서 Zustand에서는 하나의 스토어를 중앙 집중형으로 활용해 이 스토어 내부에서 상태를 관리하고 있음
  • Zustand는 특별히 많은 코드를 작성하지 않아도 빠르게 스토어를 만들고 사용할 수 있다는 장점이 있음

'Study > 모던 리액트 Deep Dive' 카테고리의 다른 글

모던 리액트 Deep Dive - Next.js  (0) 2025.11.23
모던 리액트 Deep Dive - 서버 사이드 랜더링을 위한 리액트 API  (0) 2025.11.10
모던 리액트 Deep Dive - 리액트 훅과 고차 컴포넌트  (0) 2025.11.01
모던 리액트 Deep Dive - 리액트 훅  (1) 2025.10.22
모던 리액트 Deep Dive - 메모이제이션  (0) 2025.10.14
'Study/모던 리액트 Deep Dive' 카테고리의 다른 글
  • 모던 리액트 Deep Dive - Next.js
  • 모던 리액트 Deep Dive - 서버 사이드 랜더링을 위한 리액트 API
  • 모던 리액트 Deep Dive - 리액트 훅과 고차 컴포넌트
  • 모던 리액트 Deep Dive - 리액트 훅
happy_dev
happy_dev
복사하고 붙여넣기 잘하고 싶어요
  • happy_dev
    happy의 개발일지
    happy_dev
  • 전체
    오늘
    어제
    • 전체 (43)
      • Java (0)
      • React (1)
      • DB (1)
      • Study (41)
        • 친절한 SQL 튜닝 (9)
        • 모던 리액트 Deep Dive (18)
        • 젠킨스로 배우는 CI,CD 파이프라인 구축 (14)
        • Studyd (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    리액트
    oracle
    조인
    젠킨스
    DB
    Jenkins
    SQL
    toad
    인덱스의기본
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
happy_dev
모던 리액트 Deep Dive - 상태관리
상단으로

티스토리툴바