"Life is Full of Possibilities" - Soul, 2020

IT (프론트엔드)

[React Deep Dive] 5. 상태 관리

m2ndy 2024. 11. 6. 15:01

 

 

모던 리액트 Deep Dive를 학습하고 정리한 글입니다.

 

5.1 상태 관리가 필요한 이유

  • 상태 : 어떠한 의미를 지닌 값. 어플리케이션의 시나리오에 따라 지속적으로 변경될 수 있는 값.
  • 상태로 분류될 수 있는 것들
    • UI : 다크/라이트 모드, 라디오, input 등
    • URL : 브라우저에서 관리되고 있는 상태 값
    • form : 로딩 중인지, 제출되었는지, 접근이 불가능한지, 값이 유효한지 모두 상태로 관리됨
    • 서버에서 가져온 값 : 대표적으로 API 요청
  • 상태 관리의 역사
    • Flux 패턴의 등장
      • MVC 패턴은 모델과 뷰가 많아지면 복잡도 증가
      • 이렇게 MVC 패턴을 적용한 어플리케이션이 비대해지고 상태도 많아짐에 따라 상태를 추적하기 어려워 짐
      • 페이스북은 이 문제를 양방향 데이터 바인딩을 원인으로 봄
      • 컨트롤러에서 모델로는 단방향이지만, (컨트롤러 → 모델) 뷰와 모델이 서로 양방향으로 변경시킬 수 있기 때문에 관리가 어려워짐
      • 따라서 단방향으로 데이터 흐름을 변경하는 것을 제안 ⇒ Flux 패턴
      • 단방향으로 하게 되면 데이터의 흐름을 추적하기 쉽고 코드 이해가 수월해짐
    • Flux 패턴
      • 💡 Action → Dispatcher → Model → View → Action …  
        • Action : 어떤 작업을 처리할 액션과 이 액션이 발생되었을 때 함께 포함시킬 데이터를 의미
        • Dispatcher : Action을 Store로 보내는 역할. 콜백 함수 형태
        • Store : 실제 상태에 따른 값과 상태를 변경할 수 있는 메서드를 가짐
        • View : 컴포넌트. Store에서 만들어진 데이터를 화면에 렌더링하는 역할
    • Redux 등장
      • 리덕스도 처음에는 Flux 구조를 구현하기 위해 만들어진 라이브러리 중 하나였다
      • Flux 구조에 Elm 아키텍처를 도입한 것이 리덕스
        • Elm : 웹 페이지를 선언적으로 작성하기 위한 언어
      • 하나의 상태 객체를 Store에 저장 → 이 객체를 업데이트 하는 작업을 dispatch 하여 업데이트 수행 ⇒ 이러한 작업을 Reducer 함수로 발생시킴
        • Reducer 함수 : 상태에 대해 완전히 새로운 복사본을 반환한 뒤, 새로운 복사본이 된 상태를 어플리케이션에 전파
      • prop drilling 해결, connect만 쓰고도 Store에 접근할 수 있게 됨
      • 반면, 보일러플레이트가 많음
    • Context API
      • 전역 상태를 하위 컴포넌트에 주입 가능 — Context Provider
      • 하지만 상태 관리가 아닌 상태 “주입”을 도와주는 기능
      • 렌더링을 막아주는 기능이 없음
    • React Query, SWR
      • fetch를 관리하는데 특화된 라이브러리
        • API 호출에 대한 상태를 관리하기 때문에 HTTP 요청에 특화된 라이브러리라 볼 수 있다
      • 캐시를 활용한다는 점에서 상태 관리 라이브러리보다는 제한적이지만 그래도 상태 관리 라이브러리라고 볼 수 있다
    • Recoil, Jotai, Zustand, Valtio 등 상태 관리 라이브러리 등장
      • 훅을 활용하여 작은 크기의 상태를 효율적으로 관리할 수 있게 됨

 

5.2 리액트 훅으로 시작하는 상태 관리

 

useState와 useReducer

  • 컴포넌트 내부의 지역 상태 관리 가능
  • 하지만, 훅을 사용할 때마다 컴포넌트별로 초기화 ⇒ 컴포넌트에 따라 다른 상태를 가질 수밖에 없다
  • 해당 컴포넌트보다 상위에 상태를 만든 뒤 하위 컴포넌트로 뿌려줄 수 있기는 하다. 하지만 제한적

 

상태 관리 라이브러리 — Recoil, Jotai, Zustand

  • Recoil, Jotai : Context와 Provider, 훅을 기반으로 가능한 작은 상태를 효율적으로 관리하는 것에 초점
  • Zustand : 리덕스와 비슷하게, 하나의 큰 스토어를 기반으로 상태를 관리
    • 이 큰 스토어는 Context가 아닌 스토어가 가지는 클로저를 기반으로 생성됨

Recoil (0.7.5 버전 기준으로 작성)

  • 최소 상태 : Atom
  • 아직 실험단계, 현재 메타에서 지원을 끊음
  • Selector를 필두로 다양한 비동기 작업을 지원하는 API 제공 ⇒ 리덕스와 달리 추가적인 미들웨어를 사용하지 않더라도 비동기 작업 비교적 간단하게 처리 가능
  • RecoilRoot
    • Recoil을 사용하기 위해 RecoilRoot를 최상단에 선언해 두어야 함
    • Context를 만들어 상태값을 저장하기 위한 스토어를 context에 생성해줌
    • 스토어의 상태값에 접근할 수 있는 함수가 있음 - 상태 접근, 변경 가능
    • 값이 변경되면 이 값을 참조하고 있는 하위 컴포넌트에 모두 알림
  • Atom
    • 상태를 나타내는 Recoil의 최소 단위
    • key 값을 필수로 가짐 — 다른 atom과 구별되는 식별자. 유일한 값이어야 함
  • Selector
    • Atom 값을 바탕으로 새로운 파생 상태를 조립할 수 있음
    • useStateSelector와 유사한 역할
  • useRecoilValue
    • atom의 값을 읽어오는 훅
  • useRecoilState
    • atom의 값을 가져고오 변경할 수 있는 훅

Jotai (1.8.3 버전 기준으로 작성)

  • Recoil의 atom에서 영감을 받아 만들어짐
  • 타입스크립트 기반으로 작성됨
  • 상향식 (bottom-up) 접근법
    • 리덕스처럼 하나의 큰 상태를 내려주는 것이 아니라, 작은 단위의 상태를 위로 전파하는 구조
    • 리액트의 context의 문제점인 불필요한 리렌더링을 해결하고자 설계
    • 메모이제이션이나 최적화를 시키지 않아도 리렌더링X
  • Atom
    • Jotai에서는 atom 하나만으로 상태와 파생 상태 모두 만들 수 있음
    • 별도의 key가 필요 없음
  • useAtomValue
  • useAtom
    • useState와 동일한 형태의 배열을 반환 [값, set]

Zustand(4.1.1 기준으로 작성)

  • 리덕스를 바탕으로 만들어짐
    • 하나의 스토어를 중앙 집중형으로 활용하여 스토어 내부에서 상태를 관리
    • 미들웨어 지원 ⇒ 기본적인 상태 관리 작업 외 추가적인 작업 정의 가능 (sessionStorage 저장 등)
  • 라이브러리 크기가 작은 편
  • 리덕스보다 간단하고 빠르게 상태 정의 가능
  • 타입스크립트 기반으로 작성됨

라이브러리 선택 시 maintainer가 많고 다운로드가 활발하며, 이슈 관리가 잘되고 있는지를 고려하는 것이 좋다.