오늘은 ‘모던 리액트 Deep Dive’를 읽으면서, 그리고 내가 프로젝트를 경험하면서 알게된 내용을 정리해보려한다.
상태(state)란?
어떠한 의미를 지닌 값으로 애플리케이션의 시나리오에 따라 변경될 수 있는 값이다. 따라서 우리는 이러한 상태를 변경시키고 관리하기 위해 다양한 방법을 사용하는데, 가장 대표적인 방법이 react의 useState(), 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으로 해결했을 텐데, 앞으로 나도 이런 개발 환경 개선에 도움이 되는 라이브러리를 만드는데 기여해보고 싶다.
'React' 카테고리의 다른 글
[React] React의 메모이제이션 - useMemo(), useCallback(), React.memo (0) | 2025.02.19 |
---|---|
[React] JSX 문법과 컴포넌트들 알아보기 (0) | 2025.02.18 |
[React] SSR을 위한 리액트 API (1) | 2024.11.01 |
[React] 리액트 서버 컴포넌트(RSC)와 서버 사이드 렌더링(SSR) (5) | 2024.06.30 |