RIP Cheems
별도의 상태 관리 라이브러리 없이 state를 사용할 때 발생할 수 있는 몇 가지 문제가 있습니다.
props drilling
으로 인한 관심사 분리 불가능, 성능 저하 문제props drilling
은 상위 컴포넌트로부터 하위 컴포넌트로 상태를 전달하고자 할 때, 직접적으로 전달할 수 없으므로 두 컴포넌트 사이의 여러 컴포넌트를 거쳐 전달할 때 발생합니다.관심사 분리의 원칙
이 있습니다. 하나의 컴포넌트(혹은 함수, 파일)가 본연의 기능과 역할만 수행할 수 있도록 하는 것 또한 관심사 분리의 일환입니다. 이러한 관심사 분리가 제대로 이루어지지 않으면, 코드를 읽는 입장에서는 이 컴포넌트가 어떤 역할을 하는지 직관적으로 파악하기 어렵습니다. 상기에 예로 든 경우에도, 특정 컴포넌트가 어떤 역할을 하는지 알고 싶은데 해당 컴포넌트의 주 역할과 직접적으로 관련이 없는 상태를 가지고 있다면 역할을 제대로 파악하기 어려울 것입니다.useState
는 단순한 상태를 처리하기에는 적합하지만, 애플리케이션의 구조가 복잡해지면 상태 로직을 처리하기 어려워집니다. 예를 들어, 상태가 다른 상태에 의존하는 경우가 있으면 이러한 종속성을 추적하고 관리하기가 어려울 수 있습니다.컴포넌트 간 결합도
를 높일 수 있습니다.해당 상태를 부모 컴포넌트로 전달하고
다시 props로 자식 컴포넌트에 전달하는 방식을 취해야 합니다.
이러한 방식은 컴포넌트가 서로 의존하도록 만들어 재사용하기 어려워지고, 코드를 복잡하게 하여 가독성이 떨어집니다.
앞서 말한 props drilling, 복잡한 상태 로직 처리, 상태 공유의 어려움과 같은 문제를 해결하기 위해 상태 관리 라이브러리를 사용할 수 있습니다. 상태 관리 라이브러리에는 대표적으로 Redux, MobX, Recoil, jotai, zustand 등이 있습니다.
상태 관리 라이브러리 중 우리가 학습한 Redux
에 대해서 복기해봅시다. Redux는 Store
라는 일종의 저장소를 만들고, UI 내에서 발생한 Action
을 Reducer
로 전달한 후 Action의 종류에 따라 알맞은 방식으로 상태를 업데이트 합니다. 이렇듯 특정 컴포넌트 내에서 로컬로 상태를 관리하는 대신 별도의 공간에서 관리하는 방식은, 기존처럼 복잡한 상태 전달 구조가 없이 필요한 컴포넌트에서 useSelector
로 상태를 불러오는 것 만으로 해당 상태를 활용할 수 있습니다.
Redux는 불변성immutability
을 유지하는 상태 업데이트를 강제합니다. 이는 상태 변화를 추적하기 쉽게 만들고, 디버깅을 용이하게 해줍니다. 또한 중앙 집중화된 상태 관리를 제공하므로, 컴포넌트 간 데이터 흐름을 예측 가능하게 만들어주며, 상태 업데이트 로직을 표준화하여 유지보수성을 높입니다.
Redux, Mobx 등의 대표적인 상태 관리 라이브러리의 성능 자체에 문제가 있는 것은 아닙니다. 오히려, 페이스북에서 고안한 Flux 패턴을 기반으로 설계되어 안정적인 전역 상태 관리가 가능했습니다.
그럼에도 불구하고 Redux의 사용도와 만족도가 감소하게 된 요인은 다음과 같습니다.
React 전용 라이브러리가 아니다
: React 관점에서는 Store가 외부 요인으로 취급되어, 동시성 모드를 구현하기가 어렵다는 단점이 있습니다.복잡한 보일러 플레이트 초기 세팅이 필요하다
: Store, Action, Reducer 등 다양한 구성요소가 필요합니다. Redux 환경을 설정할 때, 세 개 요소를 위해 각각의 디렉토리와 파일을 생성하는 과정을 경험해보신 적이 있을 겁니다. 이러한 일련의 과정을 보일러 플레이트 초기 세팅이라고 합니다.비동기 데이터에 추가 리소스가 요구된다
: Redux-thunk, Redux-saga 등 전역 상태에 비동기 데이터를 호출하기 위한 서드 파티 라이브러리를 요구합니다.상기한 Redux의 단점을 보완한, React에 좀 더 최적화된 전역 상태 관리 라이브러리인 Recoil
을 소개합니다.
Recoil은 우선 React 전용 라이브러리인 만큼 React 내부 접근성이 높으며, React 동시성 모드, Suspense 등을 지원하기 때문에 사용자 경험 관점에서도 매우 유리합니다. 또한 전역 상태의 설정이 편리하며, Recoil이 지원하는 React Hooks
로 상태를 get/set 하기 때문에 React 문법과 유사하여 사용법을 익히기도 쉽습니다.
Recoil 공식 문서에서 정리하는 Recoil의 장점은 다음과 같습니다.
증분 및 분산
되므로 코드 분할
이 가능Atoms
라고 불리는 작은 단위로 분할됩니다. 이러한 Atoms는 각각 독립적으로 캡슐화되어 있으므로, 다른 Atoms와 완전히 별개로 존재할 수 있습니다. 이는 코드의 분할을 가능하게끔 합니다.코드 분할
과 같은 기술을 사용하여 필요한 상태만을 로드하고 나머지는 나중에 로드하는 등 편리하게 사용할 수 있습니다.Selectors
라는 개념을 사용하여 상태에서 파생된 데이터를 생성할 수 있습니다. 이렇게 만들어진 데이터는 상태를 직접 수정하지 않아도 되고, 컴포넌트에서 간단하게 사용할 수 있습니다.동기적
으로 값을 계산할 수 있고, 비동기적
으로도 값을 계산할 수 있습니다. 예를 들어, 비동기적으로 데이터를 불러와서 처리해야 하는 경우에도 Selector 함수를 사용하면 비동기적으로 데이터를 가져와 계산한 후 이를 사용하는 컴포넌트에서 값을 호출할 수 있습니다.Atoms를 분산시키는 Recoil의 모습 (상상도)
물론 그런 것은 아닙니다. (어디 가서 그렇게 말하면 누가 화낼수도 있어요!) 서로 다른 특성을 가지고 있는 라이브러리이기 때문에, 각각의 특징을 고려하여 적재적소에 사용할 필요가 있습니다.
Redux는 이미 많은 사용자와 생태계를 갖춘 성숙한 라이브러리입니다. 따라서 Redux는 많은 사용자들에게 익숙하고, 풍부한 리소스와 지원이 제공됩니다. 반면 Recoil은 최근에 출시된 비교적 새로운 라이브러리로, Redux에 비해 아직 상대적으로 적은 수의 사용자와 생태계를 가지고 있습니다.
또한, 상태 업데이트 방식에서도 두 라이브러리는 차이를 보입니다. Recoil은 React Hooks를 사용하여 상태를 업데이트합니다. 이는 간편하게 상태를 업데이트할 수 있다는 장점이 있지만, Redux처럼 불변성을 강제하지 않기 때문에 상대적으로 상태 변화에 따른 데이터 흐름을 추적하기 어려울 수 있으며, 이에 따라 디버깅에도 어려움이 생길 수 있습니다.
이에 더해, Recoil은 Redux와는 다르게 미들웨어를 지원하지 않습니다. 미들웨어는 비동기 처리, 로깅, 라우팅 등 다양한 고급 상태 관리 기능을 추가하는 데 사용됩니다. Recoil은 이러한 미들웨어 기능을 제공하지 않기 때문에, 여러 고급 기능을 요구하는 더 복잡한 애플리케이션을 구현할 때에는 Redux에 비해 사용성이 떨어질 수 있습니다.
즉, 앞서 말한 각 라이브러리의 장단점을 따져 볼 때, Recoil은 구조가 복잡하지 않은 소규모의 프로젝트에 더 적합하고, Redux는 고급 기능과 높은 확장성, 쉬운 유지보수를 요구하는 대규모의 프로젝트에 더 적합하다고 볼 수 있겠습니다.
어떤 라이브러리나 프레임워크가 무조건 우월하다거나, 무조건 열등하다는 생각은 자칫 위험할 수 있습니다. 특정 환경에서는 옳은 판단일 수 있지만, 각각의 라이브러리나 프레임워크가 가지는 특성이 상이하기에 상황과 목적에 따라 유연하게 사용하는 것 또한 개발자로서 중요한 역량이라 할 수 있습니다. 여러 라이브러리를 다양하게 사용해보고, 어디에 써야할 지 생각해보면 좋을 것 같습니다.
수정이 필요한 부분이 있거나, 다른 의견이 있으시면 말씀해주세요!