본문 바로가기
React-study/dil

[모던리액트 Deep Dive] 5장 리액트와 상태 관리 라이브러리

by 어느새벽 2024. 11. 14.

 

5.1 상태 관리는 왜 필요한가?

  • 상태는 어떠한 의미를 지닌 값으로 애플리케이션의 시나리오에 따라 지속적으로 변경될 수 있는 값을 의미한다.

5.1.1 리액트 상태 관리의 역사

 

Flux 패턴의 등장

 

양방향 데이터 바인딩이 아닌 단방향으로 데이터 흐름을 변경함

Action -> Dispatcher -> Model -> View

 

  • 액션(action) : 어떤 작업을 처리할 액션과 그 액션 발생 시 함께 포함시킬 데이터를 의미한다. 액션 타입과 데이터를 각 정의해 dispatcher로 전달한다.
  • 디스패처(dispatcher) : 콜백 함수 형태로 액션이 정의한 타입과 데이터를 모두 store로 보내는 역할을 한다.
  • 스토어(store) : 실제 상태의 값과 상태를 변경할 수 있는 메서드를 가지고 있다.
  • 뷰(view) : 리액트 컴포넌트에 해당하는 부분으로, 스토어에서 만들어진 데이터를 가져와 화면에 렌더링하는 역할을 한다.

리덕스의 등장

 

Flux 구조 구현 + Elm 아키텍처 도입 

*Elm은 웹페이지를 선언적으로 작성하기 위한 언어이다.

더보기
  • 모델(model) : 애플리케이션의 상태를 의미한다.
  • 뷰(view) : 모델을 표현하는 HTML을 말한다.
  • 업데이트(update) : 모델을 수정하는 방식을 말한다.
  • 리덕스는 하나의 상태 객체를 스토어에 저장하고 이 객체를 업데이트 하는 작업을 디스패치해 업데이트를 수행한다.
  • 위 작업은 reducer 함수로 발생시킬 수 있는데, 이 함수의 실행은 웹 애플리케이션 상태에 대한 완전히 새로운 복사본을 반환한 뒤, 애플리케이션에 이 새롭게 만드렁진 상태를 전파한다.

Context API와 useContext

  • props로 상태를 넘겨주지 않아도 원하는 곳에 Context Provider가 주입하는 상태를 사용할 수 있다.

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

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

  • useState를 사용하여 커스텀 훅을 만들어 어디서든 재사용할 수 있다는 큰 장점이 있다.
  • 훅 내부에서 관리해야 하는 상태가 복잡하거나 상태를 변경할 수 있는 시나리오가 다양해진다면 훅으로 코드를 분리, 격리해 제공할 수 있다.
  • useState와 useReducer을 기반으로 한 훅은 사용할 때마다 컴포넌트별로 초기화되므로 컴포넌트에 따라 서로 다른 상태를 가진다. 이 지역 상태(local state)는 해당 컴포넌트 내에서만 유효하다는 한계가 있다.

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

 

  • useState와 useReducer는 지역 상태이므로, 해당 컴포넌트 내에서만 유효하다는 한계가 있다 .
  • 이 한계는 useState가 리액트의 클로저 내부에서 관리되기 때문에 발생한다 .
  • 저자는 이 문제를 해결하기 위해 useState의 상태를 외부로 분리하는 방법을 제안하며, 상태가 다른 자바스크립트 실행 문맥에서 초기화되고 관리될 수 있다면 더 넓은 스코프 내에서 객체의 값을 공유할 수 있다고 설명한다.
  • 이 접근법을 구현하기 위해서는 상태가 컴포넌트 외부에 위치해야 하며, 정상적으로 리렌더링이 되어야 한다는 조건이 있다 .
  • 상태를 외부에서 자연스럽게 참조하고 렌더링하기 위해서는 세 가지 조건을 만족해야 한다.
더보기

첫 번째 조건은 여러 컴포넌트가 함께 사용할 수 있도록, 컴포넌트 외부에 상태가 존재해야 한다.

두 번째 조건은 상태가 변할 때, 해당 상태를 참조하는 컴포넌트가 최신 값을 반영하기 위해 리렌더링되어야 한다. 

마지막 조건은 객체 상태의 속성이 변해도 그 속성을 참조하는 컴포넌트가 없다면, 해당 컴포넌트는 렌더링이 되지 않아야 한다.

  • 예를 들어, A 컴포넌트와 B 컴포넌트가 각각 스몰 A와 스몰 B라는 상태를 공유하고 있을 때, 스몰 A의 값이 변경되어도 B 컴포넌트는 리렌더링되지 않아야 한다.

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

  • Context를 활용해 해당 스토어를 하위 컴포넌트에 주입한다면 컴포넌트에서는 자신이 주입된 스토어에 대해서만 접근할 수 있게 된다.
  • 이 방식은 반드시 하나의 스토어만 가지게 되며 각 스토어는 마치 전역 변수처럼 작동하여 동일한 형태의 여러 개의 스토어를 가질 수 없게 된다.
  • Context로 컴포넌트 트리 내 상태 격리가 필요한 부분에 Provider 생성한다. 각기 다른 초기값을 설정하여 개별 관리한다.

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

  • Recoil, Jotai은 Context, Provider 훅을 기반으로 가능한 작은 상태를 관리한다.
  • Zustand는 리덕스와 비슷하게 하나의 큰 스토어를 기반으로 상태를 관리한다(클로저기반).

Recoil

  • 컴포넌트는 Recoil에서 제공하는 훅을 통해 atom의 상태 변화를 구독하고, 값이 변경되면 forceUpdate와 같은 기법을 통해 리렌더링을 실행해 최신 atom 값을 return 한다.

RecoilRoot

  • 애플리케이션 최상단에 RecoilRoot를 생성한다.
  • RecoilRoot로 생성된 Context 스토어에 상태 값을 저장한다.
  • 스토어의 상태값에 접근할 수 있는 함수들이 있으며, 이 함수를 활용해 상태값에 접근, 변경이 가능하다.
  • 값의 변경이 발생하면 참조하고 있는 하위 컴포넌트에 모두 알린다.

atom

  • 상태를 나타내는 Recoil의 최소 상태 단위이다.
  • key 값을 필수로 가진다.
  • default는 atom의 초깃값이다. 

useRecoilValue

  • atom의 값을 읽어오는 훅이다.
  • 내부 useEffect를 통해 recoilValue 변경 시 forceUpdate를 통해 렌더링을 강제 수행한다.

useRecoilState

  • 값을 가져오거나 변경할 수 있는 훅이다.
  • 가져오는 값은 useRecoilValue로 사용한다.
  • 업데이트는 useSetRecoilState로 수행한다. 내부에 있는 setRecoilValue는 queueOrPerformStateUpdate 함수를 호출해 상태를 업데이트하거나 업데이트가 필요한 내용을 등록한다.

Jotai

  • 상향식 접근법이다.
  • 작은 단위의 상태를 위로 전파할 수 있는 구조다.
  • Context의 문제점인 불필요한 리렌더링 문제를 해결하기 위해 설계됐다.
  • Recoil과 달리 별도의 키 관리가 필요없고, 객체의 참조를 통해 값을 관리한다.
  • selector 없이 atom만으로 파생된 값 생성이 가능하다.

atom

  • Recoil과 같은 최소 상태 단위이다.
  • atom 하나로 파생된 상태까지 생성 가능하다.
  • key 값이 필요없다.
  • useAtomValue에 상태를 저장한다.

useAtomValue

  • version은 스토어의 버전을 말한다.
  • valueFromReducer는 atom에서 get 수행 시 반환하는 값이다.
  • atomFromReducer는 atom 그자체이다.
  • atom 값은 훅 내부의 store에서 WeakMap 방식으로 별도의 키 없이 값을 저장한다.
  • rerenderIfChanged는 넘겨받은 atom이 Reducer를 통해 스토어 atom과 달라지는 경우, subscribe를 수행하는 값이 변경된 경우 리렌더링을 유발한다.

useAtom

  • useState와 동일한 형태로 첫번째 값은 useAtomValue 훅의 결과를 반환하고, 두번째 값은 useSetAtom 훅을 반환하며 atom 수정이 가능하다. 
  • setAtom에서 사용하는 write 함수에서는 스토어에서 해당 atom을 찾아 직접 값을 업데이트 한다.

Zustand

  • 하나의 스토어를 중앙 집중형으로 활용해 스토어 내부에서 상태를 관리한다.
  • 내부의 partial, replace로 state의 일부분 또는 전체를 변경할 수 있다.
  • useStore에서는 useSyncExternelStoreWithSelector를 사용한다. useSyncExternalStore와 다른 점은 원하는 값을 가져올 수 있는 selector와 동등 비교를 할 수 있는 equalityFn 함수를 받는다는 것이다.
  • create 내에 반환값, getter, setter 함수를 작성한다.