React

[React] 상태 관리 라이브러리의 사용 의의와 그 사용법

leejin_rho 2024. 7. 7. 23:52

오늘은 ‘모던 리액트 Deep Dive’를 읽으면서, 그리고 내가 프로젝트를 경험하면서 알게된 내용을 정리해보려한다.

상태(state)란?

어떠한 의미를 지닌 값으로 애플리케이션의 시나리오에 따라 변경될 수 있는 값이다. 따라서 우리는 이러한 상태를 변경시키고 관리하기 위해 다양한 방법을 사용하는데, 가장 대표적인 방법이 reactuseState(), useContext()이다.

 

Flux 패턴

웹 애플리케이션이 비대해지면서 관리해야하는 state들이 늘어났고, 이는 매우 복잡했다. 페이스북 팀은 이러한 문제의 원인을 양방향 데이터 바인딩으로 봤다. (HTML(view) ↔ JS(model)) 따라서 이를 해결하기 위한 단방향 바인딩 방법을 제안한 것이 Flux 패턴의 시작이다.

Action → Dispatcher(액션 실행) → Store(상태 관리 정의) → View

 

기존 양방향 바인딩

Redux

Flux 구조를 구현하기 위해 만들어진 라이브러리 중 하나이다. Elm 아키텍처를 도입했다는 것이 특징.

Elm은 웹페이지를 선언적으로 작성하기 위한 언어다. Elm 또한 Flux와 마찬가지로 데이터 흐름을 3가지로 분류하여 단방향으로 강제한다.

만들어진 이후 prop-drilling 문제를 해결할 수 있었고, 하위 컴포넌트에 전파하기 쉬워졌기 때문에 오랜 시간동안 표준처럼 굳어져 사용되었다.

 

리액트 훅

오랜 기간 상태관리를 위해 리덕스에 의존했지만 훅들이 생겨났다.

useState(), useReducer()

상태 관리를 위한 가장 기본적인 방법이다. 모두 지역 상태를 관리할 수 있게 해주며 중요한 것은 useState는 사실 useReducer로 구현됐다는 사실이다.

// useReducer를 사용한 부분은 이런 느낌..! 초기값을 받았을 경우에만 초기값을 설정해주고, 
// set함수를 받았다면 그 전 값에 action을 해준다.(dispatch)

const [state, dispatch] = useReducer(
(prev: T, action: Initializer<T>) ⇒ 
typeof action === ‘function’ ? action(prev) : action, initialState,)

 

하지만 이러한 지역 상태 관리 훅의 경우에는 여러 컴포넌트가 동시에 사용할 수 없다. 이를 위해 필요한 것이 전역 상태관리이다.

현재 리액트의 useState는 리액트가 만든 클로저 내부에서 관리된다. 만약 이 값을 클로저가 아닌 다른 곳에서 초기화돼서 관리된다면 공유할 수 있을 것이다. 하지만 단순히 외부에서 초기화하는 것 뿐만 아니라, 이 값이 여러 컴포넌트에서 사용되기 때문에 리렌더링이 필요하다.

 

자신이 관리해야 하는 상태를 내부 변수로 가진 후, 변수의 값을 가져오는 get 함수를 제공해야한다. 그리고 set 함수를 통해 내부 변수 값을 최신화하고, 이 과정에서 등록된 콜백을 실행해야한다. 그리고 이 값의 변화에 따라 컴포넌트 렌더링을 유도해야한다. 또한 같은 스토어에 값들을 저장할 수 있어야한다.

 

이를 위해 존재하는 것이 상태 관리 라이브러리이다.

 

상태 관리 라이브러리

 

프로젝트에서 직접 느낀 필요성

나는 첫 프로젝트에서 운동리스트의 정보를 받아 운동 과정을 구현했어야 했다.

 

⬇️ 프로젝트에서 구현했던 운동과정

 

[react-native] FlatList로 버튼을 누르면 움직이는 UI 리스트 만들기

사용자 맞춤형 운동 루틴 AI생성 서비스인 'fitnee' 프로젝트 진행 중 나를 막히게 한 UI 구현이 있었다.    처음에는 피그마로 정적인 이미지만 확인하고 구현을 시도했는데, 아래가 그 이미지이

2wlslog.tistory.com

 

이 과정에서 나는 데이터를 첫 페이지에서 받아 다음 컴포넌트로 전달하는 방법을 선택했다. 이는 전역 상태 관리 라이브러리의 존재에 대해 잘 몰랐기 때문인데, 구현하는 과정이 매우 복잡했고 이를 위해 index값도 함께 component 6개 사이에 넘기면서 계속들고 다니다가 index값이 data.length와 같아지면 페이지 운동 완료 화면으로 탈출했다.

 

이는 prop-drilling이라고 불리며, 간단한 데이터가 아닌 이상 아주 비효율적인 방법이었다. 이를 통해 전역 상태 관리 라이브러리에 대한 필요성을 절실히 알게 되었다.

 

그리고 프로젝트 리팩토링을 진행하며 Jotai를 이용해서 데이터를 전역처리해 이 문제를 막아주었다.

 

 

.

.

.

내가 사용해본 라이브러리는 아래 두 가지이다.

Recoil

페이스북이 만든 라이브러리로 RecoilRoot.atom, useRecoilValue, useRecoilState API를 제공한다. 변경된 상태를 상위에서 하위 컴포넌트로 전파한다.

 

RecoilRoot

이를 애플리케이션의 최상단에 놓기 위해서 App()을 로 감싸줘야한다. 이는 같은 스토어에 위치시키기 위해서인데, RecoilRoot 바깥, 즉 해당 스토어의 바깥에 있는 스토어의 아이디는 모두 에러처리한다.

 

atom

Recoil의 핵심 개념이다. 상태를 위한 최소 단위로 쉽게 key값고 default값을 설정하면 된다.

// 예시, 현재는 하나의 숫자만 관리하지만 타입을 설정할 수도 있다.

const counterAtom = atom({
  key: 'myCounter',
  default: 0,
});

 

useRecoilValue

저장된 atom 값을 받아올 수 있다.

const counter = useRecoilValue(counterAtom)

 

useRecoilState

마치 useState처럼 atom 값을 변경시킬 수 있다.

const [counter, setCounter] = useRecoilState(counterAtom)

 

Jotai

Recoil에서 더 보완되어 만들어진 방법이다. Jotai는 상향식(bottom-up) 방법을 취한다.

 

atom

딱 봐도 Recoil보다 훨씬 간단한 것을 알 수 있다. Jotai에서는 처음에 값을 정할 때 key값을 받지 않는다.

const counterAtom = atom(0)

 

useAtomValue

const counter = useAtomValue(counterAtom)

 

useAtom

// 키 값을 넣어주면 된다.
const [counter, setCounter] = useAtom(counterAtom)

 

이처럼 과거와 다르게 다양한 라이브러리가 나와 전역상태 관리가 이렇게 쉬워졌다는게 얼마나 다행인 일인지 모르겠다…!! 전역 상태관리가 불가능했을 때는 대부분 prop-drilling으로 해결했을 텐데, 앞으로 나도 이런 개발 환경 개선에 도움이 되는 라이브러리를 만드는데 기여해보고 싶다.