상태관리 레인저! 프론트엔드 상태관리 라이브러리들을 살펴보자 #1 (상태관리의 시작)

나의 개발 일지·2023년 8월 10일
0

React state를 전역 상태로 관리하고 싶다고? React 상태관리 레인저! 우리에게 맞겨라!

들어가며...

React 를 사용하는 개발자라면 누구나 마주하게 되는 순간이 하나 있다. 그건 바로 props drilling. 내가 사용해야하는 state 가 쓸데 없이 번거로운 과정을 거쳐 필요 없는 코드량 만을 증가시키는 props drilling을 거쳐야만이 원하는 곳에서 사용할 수 있을 때, 혹시 이런 생각을 하지 않았는가?

"이거 꼭 이런 방식으로 사용해야하는 건가? 왜 한 곳에서 상태를 관리하고 그곳에서 불러다가 쓸 수는 없는거지?"

좋다. 그렇다면 우리는 이제 상태관리 레인저들에게 도움을 청해야 할 때가 된 것이다.

"상태관리 레인저! 도와줘요! 내 state를 전역상태로 관리하게 해줘요!"

그런데 말입니다... 하게 해주는 일은 똑같은 거 같은데 종류가 많아도 너무 많다 (Redux, Recoil, Mobx, Zustand, Jotai, Valtio, Constate 등등...) 도대체 뭘 사용해야 좋은 거고 각 라이브러리마다 무슨 특징이 있는 걸까? 각 라이브러리의 특징들을 알아보아야 나에게 딱 맞는 상태관리 라이브러리를 찾아 사용할 수 있을 것 같다. 그래서 이번 시간에는 잘 알려져있는 상태관리 라이브러리 5 종류를 살펴보고 비교해보는 시간을 가져보도록 하자!

1. 상태관리란?

들어가는 말에서 간단하게 설명하긴 했지만, 상태관리에 대해서 조금 더 자세하게 짚고 넘어가야 이 상태관리 레인저들이 어떻게 작동하고 왜 필요한지에 대해서 보다 잘 이해할 수 있을 것이다. 이는 React 공식문서의 설명을 통해서 한 번 간단히 알아보도록 하자.

Passing props is a great way to explicitly pipe data through your UI tree to the components that use it.

But passing props can become verbose and inconvenient when you need to pass some prop deeply through the tree, or if many components need the same prop. The nearest common ancestor could be far removed from the components that need data, and lifting state up that high can lead to a situation called “prop drilling”.

Props를 전달하는 것은 UI 렌더링 트리를 따라서 데이터가 필요한 컴포넌트들에게 데이터를 배타적으로 전달할 수 있는 훌륭한 방법입니다.

하지만, Props를 전달하는 과정은 굉장히 지저분하고 불편한 과정이 될 수도 있습니다. 특히, 렌더링 트리를 따라 깊게 데이터를 내려주어야 할 때나, 많은 컴포넌트들이 같은 prop을 요구할 때 말이죠. 가장 가까운 공통된 조상은 데이터가 필요한 컴포넌트들로부터 굉장히 멀리 떨어져있을 수 있습니다. 그리고 이런 상태에서 state를 끌어 올리는 과정은 "props drilling" 이라고 불리는 과정에 처하게 만듭니다.

위에서 설명했듯이 렌더링 트리 깊은 곳에 props를 전달해주어야 할 때, 많은 컴포넌트들에게 하나의 prop을 나누어주어 사용해야 할 때 우리는 쓸모도 없이 여러 컴포넌트 들에 props를 전달해야하는 불필요한 과정을 거쳐야한다.


요런 느낌이라고 생각하면 된다. 연탄을 필요한 집에 나르기 위해서 사람들이 일일히 연탄을 전달하고 있다.


React는 이러한 props drilling 과정이 굉장히 번거롭다는 것을 인지하고 있고 자체적으로 이러한 props drilling을 해결하기 위한 API를 제공하고 있다. 바로 Context API와 useContext 이다. 이 두 가지를 잘 사용하면 우리는 다음과 같이 props drilling 없이 필요한 곳에 props를 바로 배달할 수 있는 환경을 구축할 수 있게 된다.

사용법은 Hooks 편에 간단히 적어두었다.

하지만 이러한 React Context API가 리액트의 시작부터 존재했던 API는 아니었다. 이러한 상태관리의 필요성을 느낀 개발자들은 props drilling을 해결할 수 있는 방법을 찾고 있었고 이러한 시도 끝에 상태 관리 라이브러리를 개발하게 되는데, 그 첫 라이브러리들 중 하나가 바로 'Redux' 였다. Redux가 개발된 이후 상태관리의 편의성과 중요성은 널리 퍼졌고, 많은 개발자들이 Redux를 개량하거나 새로운 상태관리 패턴을 도입하는 등에 방식을 통해 지금과 같은 React 상태관리 생태계들이 구축된 것이다. 그런 의미에서 상태관리의 조상격인 'Redux'부터 살펴보면서 나머지 친구들도 차근 차근 알아보도록 하자.

아! 그런데 본격적으로 라이브러리들을 살펴보기전에 알아두어야 할 것이 하나 더 있다.

바로 모든 상태 관리 라이브러리의 시작점이 된 'Flux 디자인 패턴' 이다. 이를 이해해야지만 본질적으로 상태관리의 의미와 도대체 왜 이러한 라이브러리와 API를 사용하는지 깊게 이해할 수 있을 것이다.



2. Flux 디자인 패턴과 상태 관리 라이브러리의 시작


사실 Redux가 개발되기 전, React가 개발된 곳인 facebook에서 Flux라는 상태관리 라이브러리가 개발되었고 사용되었다. Redux는 Flux 가 사용하고 있는 디자인패턴을 적극 차용했고 이를 React 생태계에 널리 알리고 사용되게 만들었다. 따라서, 비록 Flux가 현재 사용되고 있지는 않지만 Flux 디자인 시스템에서 부터 상태관리의 개념이 태동했다고 봐도 좋을 것이다.

그러나 Flux 디자인 패턴은 상태 관리에만 국한되는 개념이 아니라 보다 넓은 관점에서 UI를 개발해나가는 하나의 디자인 설계 패턴이다. 기존의 프로그래밍 디자인 패턴에서 발생한 오류를 해결하기 위해서 등장한 새로운 프로그래밍 디자인 패턴이기 때문이다. 즉, 상태 관리라는 개념은 react state를 단순히 어쩌고 저쩌고 하는 이야기가 아니라 프로그래밍적으로 어플리케이션의 설계 자체에서 발생한 문제를 해결하기 위해서 등장했다고 보아야한다는 말이다.

1. MVC 패턴과 Flux 패턴의 탄생

Flux 디자인 패턴이 등장하기 전, 기존에 어플리케이션을 작성하는 일반적인 시스템 디자인 방식은 'MVC 패턴' 이었다. MVC 패턴이란, Model-View-Controller 의 약자로 어플리케이션의 부분 들을 Model, View, Controller 로 역할 분담하여 나누어 관심사 분리를 달성하고 보다 유지 보수하기 쉽도록 구성하는 것이다.

  1. Model : Model은 어플리케이션 하나의 도메인의 데이터와 비즈니스로직 자체를 담당하고 관리하는 역할 을 한다. Model은 도메인이 사용하는 데이터와, 비즈니스로직, 알고리즘 들의 통합성과 일관성을 유지해야할 책임을 지며 인터랙션에 따라 데이터가 변경되면 이를 처리하고 view가 렌더링하는 데이터를 변경 한다.

  2. View : View는 Model에서 스스로를 렌더링하는데 필요한 데이터를 가져와 사용자에게 보여줄 화면을 렌더링 한다. View는 비즈니스 로직을 포함하지 않고 Model에서의 데이터가 변경됨을 확인하면 스스로를 re-rendering 하여 그에 맞는 화면을 사용자에게 보여주어야 한다.

  3. Controller : Controller는 사용자와 실질적으로 상호작용하며 Model의 데이터 변경을 가능하게 하고 그에 따라 View를 변화하게 만들어주는 역할 을 한다. Controller에는 비즈니스 로직을 포함시킬 수 있고, Model 과 View의 매개 역할을 한다.

(프론트엔드 환경에서 정확히 1:1 대응은 힘들지만 Model 은 View 와 Controller 에 연동될 States, View는 화면을 렌더링하는 컴포넌트, Controller 는 이벤트 핸들러 정도가 되지 않을까 생각한다.)

facbeook 도 이러한 MVC 패턴을 통해서 facebook 어플리케이션을 개발하였다고 한다. 실제로 facebook에서 과거 이러한 패턴이 적용 되어있는 환경에서 채팅 기능을 확장 개발하고 있었다고 하는데 읽은 채팅 갯수와 안읽은 채팅 갯수를 계산하여 알림을 보여주는 기능을 개발하면서 알 수 없는 (페이스북에 로그인 하면 안읽은 메시지가 계속 표시되지만 실제로 들어가면 그러한 메시지는 없는) 버그가 계속해서 일어났다는 것이다.

살펴보니, MVC 패턴을 적용하면 기능 확장에 취약해진다는 점이 드러났다고 한다. 보다 기능이 직관적이고 단순한 어플리케이션의 경우에는 괜찮지만 기능이 복잡해지고 연관성이 커지게 되다 보면 Model - View 사에이서 데이터의 양방향성이 존재하기 때문에 하나의 인터랙션이 발생하더라도 비즈니스 로직에 따라서 수 많은 Model 과 View가 영향 받을 수 있게된다. 이에 따라서 오류가 발생하게 되고 추적해 제거하기가 힘들어진 것이다.

위와 같은 패턴의 큰 문제점 중에 또 하나는 하나의 Controller가 다양한 Model의 상태 변경의 책임을 떠 맡아야한다는 것이었다. 하나의 Controller가 과도한 로직을 처리하게 됨으로써 상태 변경이 복잡하게 이루어질 수 있고 이러한 상황에서 비효율적인 렌더링 과정과 오류가 발생한다.

이러한 상황에서 이 Model - View 사이의 양방향성을 해결하고 데이터 플로우를 일방향성으로 두면서 Controller의 로직의 부담을 줄임과 동시에 유연하게 인터랙션에 대응하고 프로그램을 안정적으로 관리할 수 있는 디자인 패턴을 필요로 하게 되었고, 그 과정에서 Flux 디자인 패턴이 탄생하게 되었다.

2. Flux 패턴

Flux 패턴은 이러한 MVC 패턴의 단점을 극복하고 보다 더 큰 확장성을 지닌 어플리케이션을 개발하기 위해서 탄생하게되었다. Flux 패턴은 'Model - View - Controller' 세 가지로 프로그램을 구성하는 것이 아닌 'Action - Dispatcher - Store - View'의 형태를 도입함으로써 유저 인터랙션과 데이터 변경의 책임을 분리하여 관리하게 된다. 또한 'Model - View'의 데이터 플로우 양방향성을 제거하고 오로지 Store -> View 로 일관적인 한 방향으로 흐르게 만들면서 이에 따라 발생할 수 있는 혼선 또한 제거하였다. 각각의 요소를 살펴보면서 조금 더 자세하게 알아보도록 하자.

  1. Action : Action은 유저 인터랙션에 대한 정보와 새로운 데이터를 담고 있는 객체 이다. 어떠한 비즈니스 로직을 담고 있지 않으며 Dispatcher를 통해 전달되어 Store 내에서 비즈니스 로직을 처리할 때 사용된다.

  2. Dispatcher : Dispatcher는 Flux 디자인 패턴에서 모든 데이터 플로우의 흐름을 제어 하는 역할을 하게된다. Dispatcher는 Store에서 전달하는 데이터 변경 로직에 대한 콜백함수들을 전달받는데, 유저 인터랙션에 따라 Action 객체가 생성되어 Dispatcher로 전달되게 되면 해당 데이터에 맞는 Store의 콜백을 실행시킨다. Dispatcher는 관련된 기능들을 실행시키기만 할 뿐, 비즈니스 로직 자체에 대한 책임을 지니고 있지 않다.

  3. Store : Store는 어플리케이션 데이터와 비즈니스 로직 자체 를 지니고 있다. 다만 단순히 Model과 같이 하나의 도메인(관심사)에만 국한되지도 않으며, 그렇다고 모든 어플리케이션 로직 전체를 관리하지도 않는다. Store는 서로 연결되어있는 도메인들의 상태들을 관리하고, 상태 변경 로직에 대한 책임을 지니고 있다.

  4. View : MVC 패턴의 View와 비슷한 역할을 한다. 사용자에게 렌더링된 화면을 보여줄 역할을 지니고 있다. 유저의 인터랙션을 통해 Action 객체를 생성해 Dispatcher에게 전달하고, Store에서 데이터를 전달받아 변경이 있을 경우 화면을 re-render 한다.

다음과 같은 Flux 패턴을 도입함으로써 다음과 같은 목적을 이룰 수 있게 된다.

  • 데이터의 변경을 View-Controller 와 떨어뜨려 관리할 수 있기 때문에 데이터의 일관성을 유지할 수 있다.
  • 데이터 변경 로직을 모두 Store 내에서 관리하고 있기 때문에 코드 관리와 버그를 발견하기가 쉽다.
  • MVC 모델에서 복잡하게 얽혀있는 모든 View들을 Cascading Update 해야했던 이슈를 해결할 수 있다.

3. 마치며...

이렇게 상태관리의 개념과 상태관리가 시작되게 된 배경을 조금 살펴보았다. 데이터 변경과 비즈니스 로직 등등을 계속 이야기했지만, 프론트엔드 영역에서 이들은 (특히 React 어플리케이션에서는) States 들과 States들을 변경하게되는 로직 그리고 마지막으로 이에 연결되어있는 컴포넌트들일 것이다.

facebook은 이러한 Flux 패턴의 도입을 통해서 채팅 기능의 오류를 해결하고 좀 더 유연하고 안정성있게 개발할 수 있었다고 한다. 그렇다면 우리들도 상태 관리 라이브러리들을 도입하여 States 들을 보다 잘 관리하게 된다면 더 나은 어플리케이션을 개발할 수 있지 않을까? 다음 시간에는 위와 같은 개념들을 살펴보았으니 실질적으로 라이브러리들의 사용법과 차이점들을 비교해보도록 할 것이다.

profile
반갑습니다! 오늘도 좋은 하루 입니다🙋🏻‍♂️. 프론트엔드 개발을 공부하고 있습니다!

1개의 댓글

comment-user-thumbnail
2023년 8월 13일

MVC 패턴이 먼저 존재했고 MVC 패턴의 취약점을 보완하기 위해 FLUX 패턴이 등장하게 되었군요..!! 흥미롭게 읽었습니다ㅎㅎ

답글 달기