React의 상태 관리

Ocean·2023년 5월 27일
14

🐥 1. 상태란?

프론트엔드에서 상태란 무엇일까? 주로 유저 정보나 UI에 영향을 미치는 변할 수 있는 데이터이다.

상태는 특정 컴포넌트 내에서만 관리되는 로컬 상태와 여러 컴포넌트에서 관리되는 전역 상태로 구분지을 수 있다.

상태의 종류

지역 상태 (local state)

특정 컴포넌트 안에서만 관리되는 상태를 뜻하며, 다른 컴포넌트들과 데이터를 공유하지 않는다.
대부분의 경우 form(input box, select box, radio button 등)을 이용한 상태는 로컬 상태이다.

전역 상태 (global state)

전역 상태는 다른 컴포넌트와 상태를 공유하고 영향을 끼치는 상태이다.
예를 들어 장바구니에 담긴 물품의 경우, 상품 선택 여부에 따라 총 주문 금액을 업데이트해야 한다.
또한, 장바구니에 담긴 물품은 그 개수 등을 다른 컴포넌트에 전달해주어야 한다.


🐥 2. 상태관리란?

상태관리란 유저와의 상호작용을 위해, 상태를 조작하고 다루는 모든 작업을 말한다.

2.1 상태관리가 중요한 이유

상태는 언제든지 유저의 상호작용 혹은 화면의 변화에 따라 비동기적으로, 지속적으로 변할 수 있다. 만약 서비스가 방대해지고 역할들이 많아지다 보면 상태가 언제?, 어디서?, 왜? 변경되는지 추적하기 힘들어진다.

상태관리가 제대로 되지 않으면 어떤 일이 일어날까?

  • 불필요한 리렌더링 발생
  • 의도하지 않은 UI/UX 발생
  • 유지 보수하기 힘든 코드

🐥 3. 과거의 상태관리 (jQuery)

SPA가 대중화되기 이전 제이쿼리로 작성된 코드들은 상태관리를 어떻게 했을까? 제이쿼리 시대에도 이미 Ajax가 구현되어 비동기적으로 동적 화면을 구성했기 때문에 상태관리가 필요했다.

단순히 전역 객체를 사용하여 데이터를 관리할 수 있지만 전역상태를 오염시킬 수 있고, 상태를 필요로 하는 요소만 직접 관리하고 싶었기에 HTML의 data 속성을 사용했다.
data 속성은 특정한 데이터를 DOM 요소에 저장해두기 위한 개념이다.

상태가 필요한 태그의 data 속성에 상태를 넣음으로써 해당 요소에만 접근하여 상태를 관리할 수 있었다.

HTML의 데이터 속성을 사용하여 DOM element에 직접 상태를 주입하고, 사용하는 곳에서 data 속성에 있는 데이터를 받아와서 사용했다.


jQuery 상태 관리 과정

  • Element A를 선택해서 상태를 가져옴
  • Element C를 선택해서 상태를 가져옴
  • 가져온 데이터 조함
  • API 호출
  • 응답 값이 내려옴
  • 응답 값 정제
  • Element B를 선택해서 상태 업데이트

jQuery 개발은 DOM에 jQuery로 동작을 입히는 것으로 DOM이 베이스이다.

하지만, 위 방식은 크게 두 가지 문제점으로 복잡성을 키웠다.

  1. DOM으로의 접근. DOM에 상태를 저장하고 있으므로, 상태를 관리하기 위해서는 직접 접근을 해야한다. 이를 위해 DOM에 접근하는 코드가 반드시 필요하다. 결과적으로 상태가 아니라 DOM 요소를 중심으로 코드가 작성된다. 이는 상태에 대한 접근성을 떨어트리고 복잡성을 키우게 된다.

  2. 상태변화 추적이 어렵다. 상태는 여러 요소에 흩어져 있는데 만일 api를 호출하여 업데이트 중에 상태가 변경된다면 어떻게 될까? 상태가 올바르게 변경될지도 미지수이고, 버그 발생시 어디서 문제가 생겼는지 추적하기도 어렵다. 이는 코드의 유지보수와 확장을 어렵게하는 대표적인 문제 중 하나이다.


🐥 4. SPA의 등장

유저들은 즉각적인 상호작용을 원하고, 보다나은 UX를 바라기 시작했고, 이에 따라 SPA가 프론트엔드 개발의 주가 되었다.

SPA를 지원하는 프레임워크나 라이브러리들은 DOM에 접근없이도 데이터가 변경되면 값을 변경할 수 있도록 지원한다. 이를 통해 프론트엔드 개발자는 DOM이 아닌 JS에서 상태를 관리할 수 있게 되었다.

즉, DOM 중심이던 상태관리 로직이 데이터 중심의 상태관리 로직이 되었다.

따라서 어디에서 상태가 변화했는지 추적하기 유리하다.


🐥 5. React의 상태관리

리액트는 단방향의 데이터 흐름을 가진다. 상위 컴포넌트에서 State를 가지고 이에 의존하는 하위 컴포넌트가 있다면 props로 해당 state를 넘겨준다.

만약 루트 컴포넌트에서 해당 state가 필요한 컴포넌트 사이에 다른 컴포넌트들이 많이 존재하면 심각한 Props Drilling이 발생하게 된다.

Props Drilling


props drilling이란 위의 그림과 같이 props를 하위 컴포넌트로 전달하는 과정에서 몇 개의 컴포넌트를 뚫고 들어가는 형태를 말한다.
props drilling이 너무 많이 발생하면 컴포넌트 의존성이 생기고, props를 추적하는 것도 어려워진다.

Flux

데이터 수정, 변경하는 비즈니스 로직은 백엔드에서 수행하고, react는 받은 데이터를 UI 컴포넌트로 props를 활용해 보내주는 단방향 흐름을 Flux라고 한다.

Flux의 행심은 View로 데이터가 들어가지만, View에서 데이터가 나오지 않는 것이다

Flux의 흐름
1. Action은 버튼을 누르는 것과 같은 이벤트의 이름이다.
2. Dispatcher는 Action에서 발생한 이벤트의 이름에 따라 처리할 행동을 알려준다.
3. Store는 데이터가 저장되어 있는 저장소이다. Dispatcher에서 받은 행동에 따라 데이터를 핸들링한다.
4. View에서는 Store에서 데이터를 받는다. 그리고 Action을 통해 이벤트를 발생시키지만, 데이터는 전달하지 않는다.

위의 흐름을 바탕으로 조금씩 복잡해지게 상태관리가 발전하기 시작하다, 2015년 React + Flux 구조에 Reducer을 결합한 Redux가 등장한다.


🐥 6. Redux의 등장

Flux와 Redux의 가장 큰 차이는 reducer의 등장이다. 간단하게 말하자면, Flux에서는 store가 여러 개였지만, Redux에서는 하나의 store에 여러개의 reducer가 존재한다.

Redux 특징

  • Single source of truth
    : 동일한 데이터는 항상 같은 곳에서 가지고 온다. 즉, 스토어라는 하나뿐인 데이터 공간이 있다.

  • State is read-only
    : 상태는 Action이라는 객체를 통해서만 상태를 변경할 수 있다.

  • Change are made with pure function
    : 변경은 순수 함수로만 가능하다

Redux 구성

🛒 Store

Store란 상태가 관리되는 오직 하나의 공간이다.
컴포넌트와는 별개로 스토어라는 공간이 있어서 그 스토어 안에 앱에 필요한 상태를 담는다.

컴포넌트는 상태 정보가 필요할 때 스토어에 접근한다.

🛒 Action

Action이란 앱에서 스토어에 운반할 데이터를 말한다.
Action은 JS object 형식으로 되어 있다.

🛒 Reducer

Action을 Reducer에 전달해야 Store에 저장할 수 있다.
Reducer가 Action을 확인하고 Store의 상태를 업데이트 한다.
Action을 Reducer에 전달하기 위해서는 dispatch() 메소드를 사용해야 한다.

Redux 장단점

장점

컴포넌트들의 의존성을 분리한다.
Redux DevTools를 이용해 상태 변화를 추적할 수 있다.

단점

수많은 보일러 플레이트 코드가 있다.
API 통신 관련 코드를 전역적으로 관리해야 한다.

이러한 단점들을 해결하기 위해 나온 수많은 상태관리 라이브러리 중 하나가 Recoil 이다.


🐥 7. Recoil의 등장

Recoil은 Redux와는 달리 리액트만을 위해 생겨난 라이브러리이다.
Recoil을 사용하면 atoms (공유 상태)에서 selectors (순수 함수)를 거쳐 React 컴포넌트로 내려가는 data-flow graph를 만들 수 잇다.

Recoil은 Atom이라는 작은 데이터 조각을 만들어 해당 데이터 변화 시에 이를 참고하는 컴포넌트들만 re-render 시킨다.

Recoil 구성

🛒 Atoms

atom은 상태의 단위로, 값이 업데이트되면 값을 구독한 컴포넌트가 리렌더링된다.
Atoms는 atom 함수를 사용해 생성할 수 있다.
컴포넌트에서 atom을 읽고 쓰려면 useRecoilState라는 훅을 사용해야 한다.

🛒 Selectors

Selectors는 Atoms나 다른 Selectors를 입력으로 받아들이는 순수 함수이다.
상위의 Atoms 또는 Selectors가 업데이트 되면 하위의 selector 함수도 다시 실행된다.

최소한의 상태 집합만 Atoms에 저장하고, 다른 모든 파생되는 데이터는 Selectors를 통해 계산함으로써 쓸모 없는 상태의 보존을 방지할 수 있다.


🐥 8. Redux vs Recoil

Redux와 Recoil의 장단점을 살펴보자

Redux

장점 : Redux는 이미 검증된 신뢰성 있는 라이브러리이다. 또한, 상태값의 변경 사항을 Redux Devtools를 이용해 직관적으로 볼 수 있기 때문에 전역으로 관리해야 하는 상태값이 많아질 경우 디버깅이 상대적으로 Recoil에 비해 더 편할 것이다.

단점 : 작은 상태 하나를 변경하려고 해도, actions, reducer, type 등 보일러 플레이트 코드를 많이 작성해야 하는 번거로움이 있다.

Recoil

장점 : React의 useState 훅과 비슷하게 동작하는, 직관적이면서 간단한 구조를 가진다. 이에 따라 코드의 양은 매우 줄어든다.

단점 : Redux처럼 따로 안정적인 Devtool이 아직 없다!


참고자료

[10분 테코톡] 온스타의 상태관리
[Study] DOM, Rendering, State
[FE] 프론트엔드 상태 관리와 역사

profile
chick! chick!

2개의 댓글

comment-user-thumbnail
2023년 5월 27일

좋은 글 감사합니다. 잘 보고 갑니다!!

답글 달기
comment-user-thumbnail
2023년 6월 6일

This article is very clear, and i expand my knowledge and abilities. Actually the article is very real. My BK Experience

답글 달기