[React] 상태 관리 라이브러리 및 특징

Lemon·2022년 9월 11일
0

React

목록 보기
10/21
post-thumbnail

상태(State)란?

변화하는 데이터를 말합니다. UI에 동적으로 표현되는 데이터, 즉, 사용자의 액션에 따라 변경될 수 있는 컴포넌트에 부분을 나타내는 자바스크립트 객체입니다.


상태(State) 관리란?

변화하는 데이터를 관리하는 것을 말합니다. 변화하는 데이터를 알맞게 관리하기 위해 나온 개념입니다.
여러 컴포넌트 간의 데이터 전달과 이벤트 통신을 한 곳에서 관리하는 걸 의미합니다.


상태 관리는 왜 필요할까?

UI는 사용자와 끊임없이 상호작용합니다. 이전의 사용자들은 잦은 웹 페이지 이동이 있었는데, 페이지가 바귈때마다 서버에서 데이터를 가져오기 때문에 매번 페이지가 다시 그려졌습니다. 그런데 지금의 사용자는 이전의 페이지 같이 페이지 전환이 많은 웹 페이지를 사용하고싶어하지 않습니다. 데이터가 바뀌어도 페이지가 다시 렌더링 되지않고, 변하는 부분만 변하길 원합니다. 예를 들어 페이스북이나 인스타그램의 좋아요 버튼을 누른다고 해서 페이지가 새로고침 되지 않고, 실시간으로 좋아요가 반영되는 것을 말합니다.

그리고 지금은 웹 페이지의 상태들이 복잡합니다. 한 웹 페이지의 상태가 하나라면 굳이 관리할 필요가 없을 것이지만 상태가 많아지고 상태들이 서로 복잡하게 얽혀있다면 그 상태들이 상호간에 어떻게 의존하고 있는지, 상태들이 페이지 내부에서 어떻게 흘러가고 그에따라 UI가 어떻게 변하는지 알아차리기 어렵습니다. 게다가 이런 상태값들이 비동기적이라면 더욱 관리하기 힘들어 질것입니다. 하나의 페이지에서도 다양한 상태들이 존재하고 어떻게 변화하는지 제대로 알고 있어야하기 때문에 이를 효과적으로 관리할 필요가 있어졌습니다.

또한 리액트는 단방향 바인딩을 지원하기 때문에 부모에서 자식으로만 stateprops로 전달할 수 있고, 자식의 props를 부모에게 직접 전달할 수 없기 때문에 자식에서 부모의 상태를 바꾸려면 해당 상태를 컨트롤하는 함수를 props로 넘겨줘야합니다. 이것이 반복되면 props drilling이 발생한다는 문제점이 있습니다. 프로젝트의 규모가 커질수록 props의 깊이가 증가하게 되고, 이는 불필요한 리렌더링을 유발할 수도 있기때문에 상태관리가 필요합니다.


상태 관리 라이브러리 종류 및 특징

상태관리 라이브러리는 다양한 종류가 있습니다.
가장 대표적인 것은 Redux이고, 비슷한 기능을 하는 MobX, Recoil 등이 있습니다.
이 상태관리 라이브러리들은 대부분 전역 상태 관리를 지원합니다. useState는 한 컴포넌트에서 만들어지고 그 하위의 상태를 넘겨받은 컴포넌트들만 가지게 되지만, 전역 상태 관리 라이브러리를 사용하면 꼭 그렇게 인접한 컴포넌트끼리 넘겨주는 방식을 거치지 않더라도 여기저기 컴포넌트에서 접근 할 수 있습니다. 일단 Props가 복잡해지는 문제는 해결됩니다.
이런 라이브러리의 경우 진짜로 값을 할당하는 부분은 한 곳에서만 일어난다는 특징이 있습니다.
공통적인 특징상태 업데이트부터 컴포넌트 리랜더링까지 데이터의 흐름을 한 방향으로, 한 지점을 거쳐가도록 설정한다는 것입니다.


1. Redux

리덕스가 나오기 전 리액트를 포함한 대부분의 프로젝트는 MVC 아키텍처가 많이 사용되었습니다. 컨트롤러가 여러 모델을 제어하고 모델과 뷰가 서로 바라보는 구조로, 모델과 뷰가 양방향으로 영향을 미치기 때문에 프로젝트 규모가 커지고 상태가 많아질수록 관리가 어렵습니다.

그래서 페이스북이 새로 내놓은 아키텍처가 Flux 아키텍처입니다.
Flux 아키텍처데이터의 흐름이 단방향으로 흐르는 구조입니다.

이러한 Flux 아키텍처를 가져가는 대표적인 라이브러리가 리덕스입니다.

Redux 특징

  • 비동기 데이터 처리를 하기 위해서는 사가와 같은 별도의 라이브러리를 추가적으로 사용해야합니다.
  • 데이터 관리에 대해 상당히 보수적인 접근방식을 취하고 있는데, 이는 확장 및 디버깅에 있어서 강점을 가지고 있습니다
  • 불변성 유지가 중요합니다.
  • 오직 하나의 store(단일 스토어)만 가지며, 하나의 객체 트리를 가지기 때문에 디버깅에 용이합니다.
  • store에 모든 상태를 저장하는 중앙 집중 방식입니다.
    store는 외부 요소이기 때문에 리액트의 내부 스케줄러에 접근할 수 없습니다.
  • 함수형 프로그래밍입니다.
  • 스토어 내부에 상태는 action 객체에 의해서만 변경될 수 있다. 모든 상태 변화들이 하나의 store에 집중되어 있고 오직 단방향으로 일어나기 때문에 항상 예측 가능한 결과를 낳게 됩니다.
  • 리듀서는 순수함수로써 상태를 변경하는 것이 아닌 새로운 상태를 반환하게 됩니다.

순수함수란?
부수효과가 없는 함수 즉, 어떤 함수에 동일한 인자를 주었을 때항상 같은 값을 리턴하는 함수  + 외부의 상태를 변경하지 않는 함수


2. MobX

MobX의 README는 MobX를 다음과 같이 정의하고 있습니다.

Anything that can be derived from the application state, should be derived. Automatically.
→ 어플리케이션의 상태에서 파생(derived)될 수 있는 모든 것은 자동으로 파생되어야 한다.

리덕스와 달리 불변성에는 신경쓰지 않아도 될 정도로 규칙만 잘 신경쓰면 최적화가 잘 됩니다.

리덕스보다 다루기 쉬운 라이브러리지만 분명한 단점들이 존재합니다.

  • MobX의 경우 리덕스와 달리 store가 여러개가 될 수 있습니다. 이것은 분리가 용이해 편리할 수도 있는 반면 상태 변경시 다수의 스토어가 영향을 받을 수 있습니다.
  • 리덕스와 다르게 스토어의 데이터를 액션의 발행없이 업데이트 할 수 있습니다. 이는 구현은 쉽고 용이하나 테스트나 유지보수 측면에서는 문제를 일으킬 수도 있습니다.

그렇기에 장기적인 프로젝트, 유지보수 및 확장성을 고려해야 하는 프로젝트의 경우 MobX는 좋지않은 선택일 수 있습니다.

하지만 리덕스보다 러닝커브가 낮고 보일러플레이트 코드의 양 또한 적기 때문에 프로젝트의 규모가 크지 않다면 MobX를 사용하는 것은 좋은 해결책이 되리라 생각합니다.

러닝 커브란?
특정 기술 또는 지식을 실제 필요한 업무와 같은 환경에서 효율적으로 사용하기 위해 드는 학습 비용을 의미합니다.

보일러플레이트 코드란?
컴퓨터 프로그래밍에서 보일러플레이트 또는 보일러플레이트 코드라고 부르는 것은 최소한의 변경으로 여러곳에서 재사용되며, 반복적으로 비슷한 형태를 띄는 코드를 말합니다.


MobX 특징

  • React에 종속적인 라이브러리가 아닙니다.
  • 객체지향적입니다.
  • 단일스토어를 강제하지 않습니다.
    • redux와 다르게 store에 제한이 없습니다.
  • 불변성에 신경쓰지 않아도 됩니다.
  • Redux보다 사용이 쉽습니다.
    • action선언, connect, mapStateToProps, mapDispatchToProps 등 번거로운 작업들은 데코레이터로 간단하게 대체할 수 있습니다.
  • 기본적으로 클래스형 컴포넌트 기준에 맞춰져 있습니다. 함수형 컴포넌트에서 Hooks를 함께 사용하려고 하면 오류가 발생합니다 (observer API가 클래스형 컴포넌트를 리턴하기 때문이다.)
  • Hooks를 함께 사용하려면 mobx-react v6 또는 mobx-react-lite를 사용해야 합니다.
  • MobX는 절대적으로 필요한 경우에만 state를 변경합니다.
  • Typescript를 기반으로 만들어졌습니다.

3. Recoil

Facebook의 Dave McCabe가 개발한 React용 상태 관리 라이브러리입니다. 페이스북에서 비교적 최근에 나왔습니다.

리코일은 Atoms(공유 상태)Selectors(순수 함수)로 이루어져있습니다.

3-1. Atoms

Atoms은 상태의 단위이며, 업데이트와 구독이 가능합니다. 유니크한 키 값으로 구분됩니다. Atom이 업데이트되면 각각의 구독된 컴포넌트는 새로운 값을 반영하여 다시 렌더링됩니다.

Atomsatom함수를 사용해 생성합니다.

const fontSizeState = atom({
  key: 'fontSizeState',
  default: 14,
});

Atoms는 디버깅, 지속성 및 모든 atomsmap을 볼 수 있는 특정 고급 API에 사용되는 고유한 키가 필요합니다. 두 개의 atom이 같은 키를 갖는 것은 오류이기 때문에 키 값은 전역적으로 고유하도록 해야합니다. React 컴포넌트의 상태처럼 기본값도 가집니다.
컴포넌트에서 atom을 읽고 쓰려면 useRecoilState라는 훅을 사용합니다. useState와 비슷하지만 상태가 컴포넌트 간에 공유될 수 있다는 차이가 있습니다.

function FontButton() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  return (
    <button onClick={() => **setFontSize((size) => size + 1)**} style={{fontSize}}>
      Click to Enlarge
    </button>
  );
}

버튼을 클릭하면 버튼의 글꼴 크기가 1만큼 증가하며, fontSizeState atom을 사용하는 다른 컴포넌트의 글꼴 크기도 같이 변경됩니다.

function Text() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  return <p style={{fontSize}}>This text will increase in size too.</p>;
}

3-2. Selectors

Selectoratoms이나 다른 selectors를 입력으로 받아들이는 순수함수(pure function)입니다.

또는 selectors가 업데이트되면 하위의 selector함수도 다시 실행됩니다. 컴포넌트들은 selectorsatoms처럼 구독할 수 있으며 selectors가 변경되면 컴포넌트들도 다시 렌더링됩니다.

Selectors는 비동기처리 뿐만 아니라 데이터 캐싱 기능도 제공하기 때문에 비동기 데이터를 다루기에도 용이합니다.

Selectors는 상태를 기반으로 하는 파생 데이터를 계산하는데 사용됩니다. 최소한의 상태 집합만 atoms에 저장하고 다른 모든 파생되는 데이터는 selectors에 명시한 함수를 통해 효율적으로 계산함으로써 쓸모없는 상태의 보존을 방지합니다.

Selectors는 어떤 컴포넌트가 자신을 필요로하는지, 또 자신은 어떤 상태에 의존하는지를 추적하기 때문에 이러한 함수적인 접근 방식을 매우 효율적으로 만듭니다.

컴포넌트의 관점에서 보면 selectorsatoms는 동일한 인터페이스를 가지므로 서로 대체할 수 있습니다.

Selectorsselector 함수를 사용해 정의합니다.

const fontSizeLabelState = selector({
  key: 'fontSizeLabelState',
  get: ({get}) => {
    const fontSize = get(fontSizeState);
    const unit = 'px';

    return `${fontSize}${unit}`;
  },
});

get 속성은 계산될 함수입니다. 전달되는 get 인자를 통해 atoms와 다른 selectors에 접근할 수 있습니다. 다른 atomsselectors에 접근하면 자동으로 종속 관계가 생성되므로, 참조했던 다른 atomsselectors가 업데이트되면 이 함수도 다시 실행됩니다.

fontSizeLabelState 예시에서 selectorfontSizeState라는 하나의 atom에 의존성을 갖습니다. 개념적으로 fontSizeLabelState selectorfontSizeState를 입력으로 사용하고 형식화된 글꼴 크기 레이블을 출력으로 반환하는 순수 함수처럼 동작합니다.


Recoil의 특징

  • 보일러플레이트가 별로 없기 때문에 리액트 지역 상태로서 단순한 get/set 인터페이스로 상태를 공유할 수 있습니다.
  • 러닝 커브가 낮습니다.
  • 동시성 모드와 양립할 수 있는 가능성이 있습니다.
  • 상태를 분산적으로 둘 수 있기 때문에 코드 스플리팅이 가능합니다.

    스플레팅이란?
    코드에서 당장 사용하는 부분만을 로딩하고, 현재 필요하지 않은 코드 부분은 따로 분리시켜 나중에 로드함으로써 로딩시간을 개선하는 것이 코드 스플리팅입니다.

  • 아직 안정화 단계에 있지않습니다. (2020년 5월 출시)
  • 공식 문서의 예시가 부족하다는 의견이 있습니다.

결론

그래서 어떤 상태관리 라이브러리를 쓰는게 좋나요?에 대한 대답은 각 라이브러리마다 장단점이 있고, 프로젝트마다 특성이 다르니 특성에 맞게 골라 쓰면 좋을 것 같습니다!


🔗 참고 링크
https://medium.com/hcleedev/web-상태-관리-라이브러리란-개념-redux-예시-acf48c51ae14
https://velog.io/@danmin20/상태관리-라이브러리-뭘-쓸까
https://www.youtube.com/watch?v=alsCMx6vpG4
https://velog.io/@mementomori/React-상태-관리-Tool-사용-비교-Redux-VS-MobX-VS-Context-API
https://doqtqu.tistory.com/336#toc-Redux 와 MobX의 차이점

profile
프론트엔드 개발자 가보자고~!!

0개의 댓글