이 글은 저자의 허락을 받고 "State Management Battle in React 2021: Hooks, Redux, and Recoil" 이란 제목의 글을 번역하여 작성한 글입니다.
원문 링크 : https://dev.to/workshub/state-management-battle-in-react-2021-hooks-redux-and-recoil-2am0
소개
수 년간 리액트의 거대한 성장은 무엇보다도 다양한 상태 관리 라이브러리들을 만들어냈습니다.
이 글을 쓰는 시점에서 리액트에서 사용 가능한 라이브러리들는 어마어마합니다. 그러므로, 리액트 커뮤니티의 소음과 뉴스에 휩쓸리지 않기 위해 특정 프로젝트에 어떤 상태 관리 라이브러리를 선택해야 하는지 아는 것은 어플리케이션 개발을 촉진하는 데 중요한 요소입니다.
어떤 개발자들은 React Hooks 를 이용해 문제를 해결합니다. Redux 또는 새로 릴리즈 된 Recoil과 같은 상태 관리 라이브러리를 함께 사용하는 개발자도 있습니다.
이 글에서는 일반적인 React 어플리케이션에서 Redux, Hooks 및 Recoil을 사용한 상태 관리의 모범 사례에 대해서 설명합니다.
우리는 또한 다음과 같은 질문에 답하려고 노력할 것입니다.
상태 관리 라이브러리를 선택하기 전에 고려해야 할 메트릭은 무엇입니까?
이 글은 상태 관리 라이브러리가 필요한 리액트 어플리케이션을 개발하는 데 관심 있는 독자들에게 유용할 것입니다.
이 글은 리액트의 상태 관리에 대한 소개가 아닙니다. 리액트, Hooks 및 Redux에 대한 기본적인 이해가 필요합니다. 따라서 리액트와 리액트의 상태 관리에 대해 시작하는 단계라면 다음 기본적인 사항을 공부한 뒤 이 튜토리얼을 시작하시기 바랍니다.
# 간단히 말해 상태는 무엇일까요?
상태관리는 여러 컴포넌트 간에 데이터를 공유하고 커뮤니케이션하는 단순한 방법입니다. 읽기와 쓰기가 가능한 앱의 상태를 나타내는 구체적인 데이터 구조를 생성합니다. 리액트 16.8 버전 이후로, 함수형이든 클래스형이든 상관없이 모든 컴포넌트는 상태를 가질 수 있습니다.
간단하게 정의하자면, 상태는 사용자의 액션에 따라 변경될 수 있는 컴포넌트의 부분을 나타내는 Javascript 객체입니다. 상태는 단순하게 컴포넌트의 메모리라고 말할 수도 있습니다.
사용자가 일반적인 리액트 앱에서 작업을 수행하면 컴포넌트의 상태가 변경됩니다. 나쁘진 않지만, 앱의 크기가 커지기 시작하면 문제가 됩니다. 따라서, 이러한 앱의 복잡성은 의존성을 추적하는 것을 극도로 어렵게 만듭니다.
위 질문에 답변하기 위해 이커머스 어플리케이션을 구축한다고 가정합시다. 이와 같은 앱에서는 쇼핑 카트, 버튼, 뷰 카트 세션, 체크아웃, 로그인 바 등 거의 모든 요소가 구성 요소가 될 수 있습니다. 이 앱에서 한 유저가 카트에 상품을 담는 하나의 액션은 아래와 같은 많은 다른 컴포넌트에 영향을 줍니다.
- 카트 컴포넌트 자신의 상태 변경
- 유저의 카트 히스토리에 카트를 추가
- 상품을 체크아웃
이것은 우리가 이커머스 앱에 추가할 수 있는 많은 것들 중 일부만 언급한 것입니다. 담당 개발자가 개발하는 동안 확장성을 고려하지 않으면 머지않아 많은 버그와 문제에 직면할 수 있습니다. 이런 앱을 지속적으로 디버깅하고 수정하는 것은 결국 골칫거리가 될 것입니다.
위의 시나리오는 일반적인 리액트 어플리케이션에서 상태의 중요성을 보여줍니다. 이 어플리케이션의 상태를 관리하기 위해 원하는 라이브러리를 사용할 수 있습니다. 이 라이브러리들은 어떤 경우에도 자신의 일을 수행합니다.
상태가 서로 다른 두 컴포넌트에 필요하다고 할 때, 일반적으로 상태를 부모 컴포넌트로 상승시키면서 그 상태가 필요한 두 컴포넌트의 공통 부모 컴포넌트에 도달할 때까지 올라간 후 아래로 전달됩니다. 이 프로세스는 굉장히 힘들고 상태를 유지하기 어렵게 만듭니다. 데이터가 필요하지도 않은 중간 컴포넌트에 데이터를 전달해야 하는 경우도 많습니다.
앱이 커질수록 상태 관리가 엉망이 됩니다. 그렇기 때문에 Redux, Recoil과 같은 상태 관리 툴이 필요하며 이것은 상태를 더 쉽게 유지할 수 있습니다. 아래 섹션에서는 모든 상태 관리 라이브러리(Redux, Hooks, Recoil)와 해당 라이브러리의 특성, 그리고 각 라이브러리를 살펴보기 전에 고려해야 할 사항을 살펴보겠습니다.
Redux
첫 번째는 Redux 입니다. 거의 최초로 React 기반의 상태 관리 라이브러리로 자리를 잡은 지 꽤 됐습니다. Redux는 이커머스 앱의 문제점을 해결하기 위해 만들어졌습니다. Store라고 하는 Javascript 객체를 제공하며, 한 번 설정으로 어플리케이션의 모든 상태를 포함하고, 필요할 때 업데이트 합니다. 다음은 Redux의 작동 방식을 도식화 한 그림입니다.
Redux가 React와 자주 함께 사용되는 이유가 무엇인지 궁금하실 겁니다. 저의 경험상 Redux는 사용자의 액션에 반응(특히 UI에서)하며 상태 업데이트를 처리하기 때문입니다. 또한 Redux는 모든 프레임워크에서 독립 실행형 상태 관리 툴로 사용할 수 있습니다.
언제 Redux를 사용해야 할까요?
Redux는 현재 가장 인기있는 리액트 상태 관리 라이브러리 중 하나입니다. 이 섹션에서는 어플리케이션에서 Redux를 사용해야 하는 시점을 자세히 살펴보겠습니다.
첫째로, Redux를 사용하면 앱의 상태를 한 곳에서 관리하고 앱의 변경사항을 보다 예측 가능하고 추적 가능한 상태로 유지할 수 있습니다. 앱에서 발생하는 변경 사항을 보다 쉽게 파악할 수 있습니다. 하지만 불행하게도 이 모든 이점에는 특정한 제약과 단점이 따릅니다.
종종 어떤 개발자들은 Redux를 사용하는 것이 보일러플레이트 코드를 크게 만든다고 생각하며, 불필요하게 어플리케이션이 커진다고 생각합니다. 그러나 이는 어플리케이션 아키텍처를 어떻게 구성하느냐에 달려 있습니다.
Redux를 사용해야 하는 지 알 수 있는 가장 쉬운 방법 중 하나는 로컬에서 상태를 관리하는 것이 지저분해 보이기 시작할 때입니다. 어플리케이션이 커짐에 따라 컴포넌트 간 상태 공유는 힘들어집니다. 그러면 프로세스를 번거롭지 않게 만드는 방법을 찾기 시작할 것입니다. 다음 섹션에서는 Redux with React 에 대해 살펴보겠습니다.
왜 Redux를 사용해야 할까요?
Redux with React를 사용하면 상태를 끌어올리는 번거로움이 없어져 어떤 액션이 변경되었는지 쉽게 추적할 수 있으므로 앱이 간편해지고 유지관리가 쉬워집니다. 리액트에 Redux를 사용할 경우 얻을 수 있는 몇 가지 장단점에 대해 살펴보겠습니다.
# 커뮤니티 지원
React 와 Redux의 공식 바인딩 라이브러리인 React-Redux는 대규모 사용자 커뮤니티가 구축되어 있습니다. 이를 통해 도움을 요청하고, 모범 사례에 대해 배우고, React-Redux를 기반으로 하는 라이브러리를 사용하고, 다양한 어플리케이션에서 지식을 재사용할 수 있습니다. Github에서 가장 별을 많이 받은 리액트 상태 관리 라이브러리입니다.
# 성능 향상
React Redux는 성능 최적화를 보장하여 필요할 때만 연결된 컴포넌트를 다시 렌더링 합니다. 따라서 앱의 상태를 글로벌하게 유지하는 것은 문제가 되지 않습니다.
# Redux를 통한 상태 예측
Redux에서는 상태를 항상 예측 할 수 있습니다. 동일한 상태와 액션이 리듀서로 전달되는 경우, 리듀서는 순수 함수이기 때문에 동일한 결과를 얻습니다. 상태 또한 불변이며 절대 변하지 않습니다. 이것은 무한한 실행 취소와 재실행과 같은 힘든 작업을 구현할 수 있게 해줍니다. 또한 시간 여행을 구현할 수 있습니다. 즉, 이전 상태를 왔다 갔다 하며 실시간으로 결과를 확인 할 수 있습니다.
# 로컬 스토리지를 통한 상태 지속성
앱의 상태 중 일부를 로컬 스토리지에 유지하고 새로고침 후 복원할 수 있습니다. 이를 통해 카트 데이터와 같은 것을 로컬 스토리지에 저장할 수 있습니다.
# 서버 사이드 렌더링
서버 사이드 렌더링을 위해 Redux를 사용할 수 있습니다. 서버 요청에 대한 응답과 함께 앱의 상태를 서버로 전송하여 앱의 초기 렌더링을 처리 할 수 있습니다.
# 유지보수성
Redux는 코드를 설계하는 방법에 대해 엄격하기 때문에 함께 협업을 할 때 어플리케이션의 구조를 쉽게 이해할 수 있습니다. 이것을 일반적으로 유지보수를 더 쉽게 해줍니다. 또한 컴포넌트 트리에서 비즈니스 로직을 분리할 수 있습니다. 대규모 어플리케이션의 경우 어플리케이션을 보다 예측 가능하게 하고 유지 관리 하는 것이 중요합니다.
# 쉬운 디버깅
Redux를 사용하면 어플리케이션을 쉽게 디버깅할 수 있습니다. 액션과 상태를 로깅하면 코딩 오류, 네트워크 오류 및 운영 중에 발생할 수 있는 기타 버그 유형을 쉽게 이해할 수 있습니다.
로깅 외에도 time-travel 액션, 페이지 새로 고침 시 작업 지속 등을 지원하는 탁월한 개발자도구가 있습니다. 어플리케이션 규모가 커질 수록, 디버깅은 기능을 개발하는 것보다 더 많은 시간이 소요됩니다.
Redux의 이점도 있지만 모든 어플리케이션에 Redux를 추가할 필요는 없습니다. Redux 없이도 어플리케이션을 제대로 동작할 수 있습니다.
Recoil
Recoil은 상태 관리 라이브러리 커뮤니티(Context, Mobx, Redux 등과 같은 수많은 우수한 라이브러리가 있는 커뮤니티)에서 최신의 툴입니다.
Recoil에 대해 자세히 설명하기에 앞서, 이 새로운 상태 관리 라이브러리는 React의 공식 상태 관리 라이브러리는 아닙니다. 하지만 Recoil은 페이스북의 팀인 리액트를 개발한 엔지니어들에 의해 제작되고 출시되었습니다.
하지만 Redux가 React의 공식 라이브러리가 아닌 것처럼, Recoil도 React 에코 시스템 전반에 가치가 있다고 입증되면 React 매니아들로부터 대규모 채택을 받을 수 있습니다.
Recoil이 해결하는 주요 문제
러닝 커브가 있지만 다른 상태 관리 라이브러리와 마찬가지로 전역 상태 관리의 문제를 해결합니다.
짧은 기간 Recoil은 사용해본 후 다음과 같은 특장점이 아주 편리하게 생각 되었습니다.
# 리액트스러운 접근과 단순함
Recoil의 심플함은 어떤 것에도 뒤지지 않으며, 그래서 이 글의 목록에 올라와 있습니다.
Redux나 Mobx와 마찬가지로 Recoil과 함께 어떤 어플리케이션이든 구축할 수 있습니다.
그러나 Recoil은 React useState의 글로벌 버전을 사용하는 것 같은 느낌을 줍니다. 또한 Concurrent 모드를 지원하는 큰 이점이 있습니다. (이것은 아직 진행중입니다)
# 쉬운 러닝 커브
Recoil은 Redux와 Mobx처럼 엄격한 러닝 커브를 필요로 하지 않습니다. 이해하기 쉬운 Atom과 Selectors라는 개념이 있고 배울 것이 많지 않습니다.
# 앱 전체적인 상태 관찰
다른 상태 관리 라이브러리와 마찬가지로 Recoil도 앱 전체 상태 관찰을 잘 처리합니다.
Recoil을 사용하여 얻을 수 있는 또 다른 장점은 아래와 같습니다.
- Boilerplate-free API
- 분산 및 증분 상태 정의
Recoil의 핵심 개념은 Atoms 와 Selectors입니다. 이것에 대해 이 글에서 다루긴 힘드므로, 자세한 내용은 Recoil 문서를 참고하시기 바랍니다.
언제 Recoil을 사용해야 할까요?
출시한지 2년도 안되어 Recoil은 너무 성장하여 이 글을 쓰는 당시 Github에 12,000 개 이상의 스타를 받았습니다. 뿐만 아니라, 리액트 커뮤니티에서 점차적으로 대중화되고 있습니다. 개인적으로 제가 프로젝트에 Recoil을 사용한 유일한 이유는 제 코드 베이스에 Redux 보일러 플레이트가 그리 많지 않을 때입니다. 한 번 Recoil을 사용해 봤지만 끔찍한 일은 일어나지 않았고 지금까지 모든 게 잘 돌아가고 있습니다.
따라서 Recoil을 언제 사용할지는 앱의 아키텍처 결정에 달려있으며, 저처럼 심플함을 선호하는 사람이라면 Recoil을 고려해볼만 합니다.
React Hooks 사용하기
Hooks 는 리액트 라이브러리에 추가된 기능 중 가장 뛰어난 기능 중 하나입니다. Hooks는 함수형 컴포넌트에 ‘상태’를 가져다주었습니다. 이제 함수형 컴포넌트는 클래스형 컴포넌트와 마찬가지로 자체적으로 로컬 상태를 생성하고 관리할 수 있습니다. 리액트에 관심이 있는 개발자는 useState, useEffect, useReducer등을 포함하여 리액트 Hooks에 익숙해져야 합니다.
이 섹션에서는 외부 상태 관리 라이브러리를 방해하지 않고 어떻게 리액트 Hooks를 독립적으로 사용할 수 있는지 설명합니다.
라이브러리 없이도 리액트 Hooks를 기본 상태 관리 도구로 사용할 수 있지만, 이는 리액트 Hooks에 대한 경험과 이해도에 따라 달라집니다.
리액트 Hooks 자체로도 강력하며 외부 라이브러리가 할 수 있는 거의 모든 것을 할 수 있습니다.
다른 상태 관리 도구에는 몇 가지 장점이 있습니다. 그러나 상태 관리 라이브러리를 사용하기 위한 여러 절차때문에 시작하기가 어렵습니다. Redux의 경우와 같이, 우리의 어플리케이션에서 사용하기 위해서는 약간의 보일러 플레이트 코드가 필요합니다. 따라서 불필요한 복잡성을 야기합니다.
반면 Context API와 리액트 Hooks를 사용하면 외부 라이브러리를 설치할 필요가 없습니다. 이를 통해 어플리케이션의 전역 상태 관리를 훨씬 간편하게 처리할 수 있습니다.
State 사용에 대해 이미 잘 알고 있다고 가정하고, 리액트에서 상태 관리 프로세스를 지원하는 두 가지 Hooks를 살펴볼 것입니다.
useReducer Hook
useReducer는 리액트 16.8버전부터 제공됩니다. Javascript의 reduce() 메소드와 마찬가지로 useReducer Hook은 인수로 두 개의 값(리듀서 함수 및 초기 상태)을 받습니다. 그리고 새로운 상태를 리턴합니다.
const [state, dispatch] = useReducer((state, action) => {
const { type } = action;
switch(action) {
case 'action description':
const newState = // do something with the action
return newState;
default:
throw new Error()
}
}, []);
위 코드에서 상태와 해당하는 메소드인 dispatch, 처리를 정의했습니다. Dispatch 메소드를 호출할 때 useReducer() 훅은 메소드가 액션 인수에서 받는 타입에 따라 작업을 수행합니다.
...
return (
<button onClick={() =>
dispatch({ type: 'action type'})}>
</button>
)
useContext
이 Hook은 프로바이더의 현재 컨텍스트를 가져오는데 사용됩니다. 컨텍스트를 만들고 제공하기 위해 React.createContext API를 사용합니다.
const myContext = React.createContext()
myContext Provider 사이에 루트 구성 요소를 넣습니다.
function App() {
return (
<myContext.Provider value={900}>
<Root />
</myContext.Provider>
)
}
컴포넌트에서 상태를 제공받기 위해 useContext Hook을 사용합니다.
function Root() {
const value = useContext(myContext)
return (
<>
<h3>My Context value: {value} </h3>
</>
)
}
useReducer와 useContext 사용
useReducer와 함께 useContext 를 사용하면 컴포넌트의 상태 관리가 다른 수준에서 이루어집니다. useReducer로 생성된 상태와 디스패치 함수를 모든 최상위 컴포넌트에 전달 할 수 있습니다. 또한 상태를 전역으로 만드는 가장 상위 컴포넌트가 될 수 있습니다. 리액트 props만을 사용해서 정보를 전달할 수도 있지만, 리액트의 Context API를 사용하면 컴포넌트 트리에 모든 것을 명시적으로 전달하지 않고도 어디서든 상태 및 디스패치 함수를 사용할 수 있습니다.
결론
이 글에서는 2021년에 가장 많이 사용되는 리액트 상태 관리 툴과 이러한 툴이 리액트 상태 관리에서 어떻게 중요한 역할을 수행하는지, 그리고 이를 프로젝트에서 언제 사용해야 하는지를 다루려고 했습니다. 일반적인 어플리케이션에서 여러분이 어떻게 상태 관리를 하는지 경험을 알고 싶습니다.
Resources
- When (and when not) to use Redux - Christian Nwamba
- React State Hooks: useReducer, useState, useContext - Robin Weiruch
- Recoil in action: Building a reusable code block component - Tomi Odunsanya
- Refactoring a Redux app to use Recoil - Ohans Emmanuel
- Why React projects still use Redux - Alexandru-Dan Pop
Article by Blessing Krofegha, originally published on JavaScript Works.