Lambda
article thumbnail

Redux Toolkit와 관련한 레퍼런스를 알아보던 중 흥미로운 Reddit Discussion을 읽었습니다. 의견 작성자는 Redux가 Context API의 어떠한 문제점을 해결해주는지에 대해 발제하였고, 이에 대해 Redux 개발팀의 maintainer가 답변을 달았습니다.

 

Redux에 대한 애정이 평소 포스팅에도 드러나 보입니다.

maintainer분이 댓글로 달아준 내용도 의견 작성자의 질문에 대한 충분한 답변이 되었지만, 레퍼런스로 남겨주신 Why React Context is Not a "State Management" Tool (and Why It Doesn't Replace Redux)에서 이에 대한 보다 심도깊은 내용을 다루고 있었기에 공유해보고 싶었습니다. 원문의 내용을 최대한 온전히 전달하고자 노력했으며, 추가적인 이해가 필요한 부분은 별도의 주석 없이 추가하여 하나의 글 처럼 편하게 읽으실 수 있도록 내용을 구성했습니다.

 

이번 포스팅에선 Redux와 Context에 대한 비교 분석을 바탕으로 왜 Context가 Redux를 대체할 수 없는지에 대해 알아보겠습니다. 

 

React Context와 Redux에 대해 알아보기 앞서

어떤 도구든 제대로 사용하려면 도구의 개발배경을 이해해야 합니다. 개발배경을 이해할 땐 다음 사항들을 중심으로 접근하는 것이 좋습니다.

  1. 목적이 무엇인지(What its purpose is)
  2. 어떤 문제를 해결하고자 하는 것인지(What problems it's trying to solve)
  3. 언제, 왜 처음 만들었는지(When and why it was originally created)

실제로 "Context vs Redux"에 대한 대부분의 혼란은 도구들이 실제로 어떤 일을 하는지, 어떤 문제들을 해결할 수 있는지에 대한 이해의 부족에서 비롯되고 있습니다. 따라서 Context나 Redux를 적절히 사용하기 위해선 각 도구들이 어떤 일을 하는지, 어떤 문제를 해결할 수 있는지에 대해 먼저 명확히 정의해야 합니다.

 

React Context란 무엇인가?

먼저, 리액트 공식 문서에서 Context에 대해 어떻게 설명하는지 살펴보겠습니다.

context를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있습니다. 일반적인 리액트 애플리케이션에서 데이터는 위에서 아래로 (즉, 부모로부터 자식에게) props를 통해 전달되지만, 애플리케이션 안의 여러 컴포넌트들에 전해줘야 하는 props의 경우 (예를 들면 locale preference, UI theme) 이 과정이 번거로울 수 있습니다. 이러한 상황에 context를 이용하면, 트리 단계마다 명시적으로 props를 넘겨주지 않아도 많은 컴포넌트가 값을 공유하도록 할 수 있습니다.

리액트 공식 문서에서는 오로지 값을 "전달(passing)"하고 "공유"하는 것에 대해서만 언급할 뿐, 값을 "관리(managing)" 하는 것에 대해서는 어떠한 언급도 하지 않고 있습니다. 이는 (현재 리액트) Context의 등장배경과도 연관이 있습니다.

 

현재의 리액트 Context API(React.createContext())는 React 16.3.에서 처음 릴리즈되었습니다. Context API는 설계에 결함이 있었던 초기 리액트의 legacy context API를 대체하기 위해 고안되었습니다. legacy context의 주요 문제는 컴포넌트가 shouldComponentUpdate를 통해 렌더링 과정을 건너 뛰면, context를 통해 전달된 값에 대한 업데이트가 "차단(blocked)"될 수 있다는 것이었습니다. 많은 컴포넌트가 성능 최적화를 위해 sholdComponentUpdate에 의존했기 때문에 legacyContext는 일반 데이터를 전달하는 데 유용하지 않았습니다. createContext()는 이러한 문제를 해결하기 위해 설계되었습니다. 이를 통해 개발자는 중간에 있는 컴포넌트가 렌더링을 건너뛰더라도 값에 대한 업데이트가 모든  자식 컴포넌트에 표시되도록(any update to a value will be seen in child components)할 수 있습니다.

 

Context 예시 살펴보기

본격적인 비교에 앞서 Context를 사용한 간단한 예시를 살펴보겠습니다.

리액트 앱에서 Context를 사용하려면 몇 가지 작업이 필요합니다.

  1. context 객체 인스턴스를 만들기 위해 const MyContext = React.createContext()를 호출합니다.
  2. 부모 컴포넌트에서 <MyContext.Provider value={someValue}>를 렌더링합니다. 이때 context에 데이터의 일부를 넣습니다. value는 어떤 값이든 상관 없습니다.
  3. provider로 감싸진 자식 컴포넌트에서 const theContextValue = useContext(MyContext)를 호출하여 값을 사용합니다.

이 때 context provider에 대한 새로운 참조를 value로 전달할 때마다 부모 컴포넌트가 다시 렌더링되고 , 해당 컨텍스트를 전달받는 자식 컴포넌트는 강제로 리렌더링됩니다.

일반적으로 다음과 같이 provider로 childComponent를 감싸도록 코드를 작성합니다.

function ParentComponent() {
  const [counter, setCounter] = useState(0);

// Create an object containing both the value and the setterconst contextValue = {counter, setCounter};

  return (
    <MyContext.Provider value={contextValue}>
      <SomeChildComponent />
    </MyContext.Provider>
  )
}

그러면 자식 컴포넌트는 useContext를 호출하여 값을 읽을 수 있습니다.

function NestedChildComponent() {
  const { counter, setCounter } = useContext(MyContext);

// do something with the counter value and setter
}

 

Context의 사용 목적과 Use Cases

위의 Context 사용 예시를 살펴보면 Context가 실제로는 아무 것도 "관리"하지 않는다는 사실을 알 수 있습니다. 비유하자면 Context는 마치 파이프와 같습니다. <MyContext.Provider>를 사용하여 파이프의 맨 꼭대기에 무언가를 놓으면, 다른 컴포넌트(자식 컴포넌트)가 useContext(MyProvider)를 사용하여 파이프의 끝자락에서 내용물을 꺼내는 구조입니다. 이처럼 Context를 사용하는 주요 목적은 "prop-drilling"을 피하는 것입니다.

개념적으로 이러한 구조는 "의존성 주입(Dependency Injection)"의 한 형태로 볼 수 있습니다. 자식 컴포넌트에는 특정 타입의 값이 필요하지만, 그렇다고 해당 값 자체를 생성하거나 설정하지는 않습니다. 대신 일부 부모 컴포넌트가 실행시간(런타임)에 해당 값을 자식 컴포넌트에 전달한다는 가정을 전제하고 있습니다.

 

Redux란 무엇인가?

Context API와의 비교를 위해,이번엔 the "Redux Essentials" tutorial in the Redux docs의 설명을 확인해보겠습니다.

Redux는 "액션(Action)"이라는 이벤트를 사용하여 애플리케이션 상태를 관리하고 업데이트하기 위한 패턴 및 라이브러리입니다. Redux는 상태가 예측 가능한 방식으로만 업데이트될 수 있도록 보장하는 규칙을 제공하고, 전체 애플리케이션에서 사용해야 하는 상태에 대한 중앙 저장소(centralized store) 역할을 합니다.

Redux는 전역 상태(애플리케이션의 많은 부분에 필요한 상태)를 관리하는 데 도움이 됩니다.

Redux에서 제공하는 패턴과 도구를 사용하면 애플리케이션의 상태가 언제, 어디서, 왜, 어떻게 업데이트되고 이러한 변경이 발생할 때 애플리케이션 로직이 어떻게 작동하는지에 대해 더 쉽게 이해할 수 있습니다.

Redux 공식문서에서 다음의 특징을 찾아볼 수 있습니다.

  1. "상태 관리"를 특정해서 언급했습니다.
  2. Redux의 목적은 시간 경과에 따른 상태변화에 대한 이해를 돕는 것이라고 명시하고 있습니다.

역사적으로 Redux는 원래 리액트가 나온 지 1년 후인 2014년에 Facebook에서 처음 제안한 패턴인 "Flux Architecture"의 구현을 위해 만들어졌습니다. 해당 발표 이후 개발 커뮤니티는 Flux에서 영감을 받은 수십 개의 라이브러리가 등장했습니다. 수많은 Flux 구현체 중 2015년에 등장한 Redux는 최고의 디자인을 가졌고, 사람들이 해결하려고 하는 문제에 대한 적절한 솔루션 제시했으며, 무엇보다도 리액트와 훌륭하게 작동했기 때문에 "Flux Wars"에서 승리하였습니다.

 

Redux와 React

Redux 자체는 UI에 구애받지 않습니다. 모든 UI 레이어(React, Vue, Angular, vanilla JS 등)와 함께 사용하거나, UI 없이 사용할 수 있습니다.

Redux는 리액트와 함께 가장 일반적으로 사용됩니다. React-Redux 라이브러리는 리액트 구성 요소가 Redux 상태에서 값을 읽고 작업을 전달하여 Redux 저장소와 상호 작용할 수 있도록 하는 공식 UI 바인딩 레이어입니다. 따라서 대부분의 사람들이 "Redux"를 언급하는 경우 일반적으로 "Redux 저장소와 React-Redux 라이브러리를 함께 사용하는 것"을 의미합니다.

React-Redux를 사용하면 애플리케이션의 모든 리액트 구성 요소가 Redux 저장소와 통신할 수 있습니다. 이는 React-Redux가 내부적으로 Context를 사용하기 때문에 가능합니다. 그러나 React-Redux는 현재 상태 값이 아니라 컨텍스트를 통해서만 Redux 저장소 인스턴스를 전달합니다. 우리는 Redux에 연결된 리액트 컴포넌트가 하나의 Redux 저장소와 통신해야 한다는 것을 알고 있지만, 컴포넌트를 정의할 때 어떤 Redux 저장소가 있는지 알지 못하거나 거의 신경 쓰지 않습니다. 실제로 Redux 저장소는 React-Redux의 <Provider> 컴포넌트를 사용하여 실행시간(런타임)에 컴포넌트 트리에 주입(injected)됩니다.

이러한 특징 덕분에 React-Redux 또한 prop-drilling을 피하기 위해 사용할 수도 있습니다. 이는 React-Redux가 내부적으로 Context를 사용하기 때문에 가능합니다. 새로운 값을 직접 <MyContext.Provider>에 명시적으로 입력하는 대신, 해당 데이터를 Redux 저장소에 넣으면 자식 컴포넌트 어디에서나 해당 값에 액세스할 수 있습니다.

 

(React-)Redux의 사용 목적과 Use Cases 

Redux 개발팀은 Redux를 다음과 같이 소개하고 있습니다 .

Redux에서 제공하는 패턴과 도구를 사용하면 애플리케이션의 상태가 언제, 어디서, 왜, 어떻게 업데이트되고 이러한 변경이 발생할 때 애플리케이션 로직이 어떻게 작동하는지 더 쉽게 이해할 수 있습니다.

Redux를 사용할 때 "prop-drilling을 피하는" 목적은 Redux를 통해 달성할 수 있는 수많은 이점 중 하나에 불과합니다.

Redux 사용을 고민해야 하는 상황을 정리하면 다음과 같습니다 .

  1. UI 레이어와 완전히 분리된 상태 관리 로직을 작성하길 원할 때
  2. 서로 다른 UI 레이어 간에 상태 관리 로직을 공유할 때(예: AngularJS에서 React로 마이그레이션 중인 애플리케이션)
  3. 액션이 디스패치될 때 추가적인 로직을 더하기 위한 Redux 미들웨어의 기능을 사용하고 싶을 때
  4. Redux 상태의 일부를 유지(persist)하고자 할 때
  5. 개발자가 리플레이할 수 있는 버그리포트를 활성화하고자 할 때
  6. 개발 중 로직 및 UI의 더 빠른 디버깅을 원할 때

 

Context가 "상태 관리"가 아닌 이유 (Why Context is Not "State Management")

'상태'는 애플리케이션의 동작을 설명하는 모든 데이터입니다 . 상태는 크게 "서버 상태(server state)", "통신 상태(communications state)", "위치 상태(location state)" 등과 같은 범주 로 나눌 수 있지만, 이들의 공통점은 데이터가 저장되고(stored), 읽고(read), 업데이트되고(updated), 사용된다(used)는 것입니다.

XState 라이브러리의 저자이자 상태 머신 전문가인 David Khourshid는 다음과 같이 말했습니다 .

상태 관리는 ‘시간이 지남에 따라 상태가 어떻게 변하는 가’에 대한 것이다.

위의 내용을 바탕으로 볼 때 "상태 관리"는 다음과 같은 특징을 가집니다.

  1. 초기값을 저장합니다.
  2. 현재 값을 읽습니다.
  3. 값을 업데이트합니다.

이러한 측면에서 React useStateuseReducer Hook은 상태 관리의 좋은 예시입니다. 우리는 두 hook을 모두 사용하여 다음과 같은 작업을 수행할 수 있습니다.

  1. 훅를 호출하여 초기값을 저장합니다.
  2. 훅를 호출하여 현재 값을 읽습니다.
  3. 제공된 setStatedispatch 또는 함수를 호출하여 값을 업데이트합니다.
  4. 구성요소가 다시 렌더링되었기 때문에 값이 업데이트되었음을 알 수 있습니다.

위의 정리한 내용에 따르면 React-Query, SWR, Apollo 및 Urql과 같은 서버 캐싱 도구(server caching tools) 또한 "상태 관리"의 정의에 부합한다고 말할 수 있습니다. 우리는 서버 캐싱 도구를 사용해 가져온 데이터를 기반으로 초기 값을 저장하고, 훅을 통해 현재 값을 반환하며, "상태 관리"를 통해 값을 업데이트 할 수 있습니다.

 

하지만 React Context는 이러한 기준을 충족하지 않습니다. 따라서 Context는 "상태 관리" 도구가 아닙니다.

앞서 설명했듯이 Context는 그 자체로는 아무것도 "저장"하지 않습니다.  실제 "상태 관리"는 useState/useReducer훅을 통해 이루어집니다.

이와 관련하여 David Khourshid는 다음과 같이 말했습니다 .

Context는 상태(이미 어딘가에 존재함)가 다른 구성 요소와 공유되는 방식입니다. Context 자체는 상태 관리와 거의 관련이 없습니다.

 

Context와 Redux 비교 

Context와 React+Redux가 실제로 어떤 기능을 수행하는지 비교하면 다음과 같습니다.

  • Context
    • 아무것도 ‘저장’하거나 ‘관리’하지 않습니다.
    • React 컴포넌트에서만 작동합니다.
    • 무엇이든(기본, 객체, 클래스 등) 될 수 있는 단일 값을 전달합니다.
    • 단일 값(single value)을 읽을 수 있습니다.
    • prop-drilling을 방지하는 데 사용할 수 있습니다.
    • 컨텍스트 값이 변경되면 값을 사용하는 컴포넌트들을 업데이트합니다. 업데이트는 건너뛸 수 없습니다.
    • side effects에 대한 메커니즘을 포함하지 않습니다.
  • React+Redux
    • 단일 값(일반적으로 객체)을 저장하고 관리합니다.
    • React 컴포넌트의 내부, 외부를 포함한 모든 UI에서 사용할 수 있습니다.
    • 단일 값(single value)을 읽을 수 있습니다.
    • prop-drilling을 방지하는 데 사용할 수 있습니다.
    • 액션을 전달하고 리듀서를 실행하는 작업을 통해 값을 업데이트할 수 있습니다.
    • 전달된 모든 작업의 기록과 시간 경과에 따른 상태 변경을 보여주는 DevTools가 있습니다.
    • 미들웨어를 사용하여 side effects를 유발할 수 있도록 합니다.

위와 같이 Context와 Redux를 비교한 결과 이들은 서로 매우 다른 특징을 가지는 도구들임을 알 수 있습니다. 실제로 둘 사이의 유일한 겹치는 부분은 "prop-drilling을 방지하는 데 사용할 수 있다"는 것입니다.

 

Use Case 요약(Use Case Summary)

마지막으로 Context와 Redux의 Use Case를 간단히 요약해 보겠습니다.

  • Context
    • prop-drilling 없이 중첩 컴포넌트에 값 전달
  • useReducer
    • 리듀서 함수를 사용한 React 컴포넌트 상태 관리
  • Context+useReducer
    • 리듀서 함수를 사용하고 해당 상태 값을 prop-drilling 없이 중첩 컴포넌트에 전달하는 React 컴포넌트 상태 관리
  • Redux
    • 리듀서 함수를 사용하여 상태 관리
    • 시간이 지남에 따라 상태가 언제, 왜, 어떻게 변했는지에 대해 추적해야하는 경우
    • UI 레이어와 완전히 독립적으로 상태 관리 로직을 작성하고 싶은 경우
    • 서로 다른 UI 레이어 간에 상태 관리 로직을 공유하는 싶은 경우
    • 작업이 전달될 때 Redux 미들웨어의 기능을 사용하여 추가하고 싶은 로직이 있는 경우
    • Redux 상태의 일부를 유지(persist)하는 기능이 필요한 경우
    • 개발 중에 로직과 UI를 더 빠르게 디버깅하고 싶을 때
  • Redux + React-Redux
    • Redux의 모든 사용 사례를 포괄하며, React 컴포넌트에서의 Redux 저장소와의 상호 작용 가능

앞서 소개한 Use Case를 바탕으로 여러분이 해결하려는 문제에 가장 적합한 도구가 무엇인지 결정해야 합니다.

아직 어느  문제상황에 어떤 도구를 사용할 지 헷갈리는 상황이라면 다음의 기준을 참고해보시는 것을 추천드리겠습니다.

  • 당신이 해야 할 유일한 일이 prop-drilling을 피하는 것이라면 Context를 사용하는 것이 좋습니다.
  • 적당히 복잡한(moderately complex) React 컴포넌트 상태가 있거나, 외부 라이브러리를 사용하고 싶지 않다면 Context +useReducer 을 사용하는 것이 좋습니다.
  • 시간이 지남에 따른 상태 변경에 대한 더 나은 추적성(traceability)을 원하거나, 상태 변경 시 특정 구성 요소만 다시 렌더링되도록 해야 하거나, 부작용 관리를 위한 더 강력한 기능이 필요하거나, 기타 유사한 문제가 있는 경우 Redux + React-Redux를 사용하세요.

저(matainer)의 개인적인 의견은 애플리케이션에서 2~3개의 상태 관련 컨텍스트를 통과한다면 이는 약한 버전의 React-Redux를 다시 개발하는 것입니다. 이러한 상황에 직면하면 Redux를 사용하는 것을 검토하는 것이 좋습니다.

 

추가) Why the Redux Toolkit is better than Context API

Why React Context is Not a "State Management" Tool (and Why It Doesn't Replace Redux) 원문에서도 마지막 부분에 언급하고 있지만 현재 Redux는 Redux Toolkit을 사용하여 구현하는 것을 권장하고 있습니다. 원문을 정리하며 Redux Toolkit를 사용했을 때 Context API에 비해 가지는 이점에 대한 궁금증이 들었고, 관련하여 좋은 레퍼런스가 있어서 해당 레퍼런스의 요약을 추가하며 마무리하고자 합니다.


Redux Toolkit이 React의 Context API 보다 나은 몇 가지 이유는 다음과 같습니다 .

  1. 예측 가능한 상태 관리(Predictable state management): Redux Toolkit은 상태 관리에 대한 엄격한 지침을 따릅니다. 그 결과 개발자는 Redux Toolkit을 사용할 때 애플리케이션이 어떻게 작동할지 더 쉽게 예측할 수 있습니다. 반면에 Context API는 Redux Toolkit에 비해 더 많은 유연성을 허용하므로 예기치 않은 동작이 발생할 수 있습니다.
  2. 단순화된 구문(Simplified syntax): Redux Toolkit은 상태 관리에 필요한 구문을 단순화하여 개발자가 작성해야 하는 상용구 코드의 양을 줄입니다. 그 결과 코드를 더 쉽게 읽고 이해할 수 있을 뿐만 아니라 오류 가능성도 줄어듭니다.
  3. 향상된 디버깅(Improved debugging): Redux Toolkit은 상태 변경을 추적하고 잠재적인 버그를 식별할 수 있는 강력한 디버깅 도구를 제공합니다.
  4. 더 나은 성능(Better performance): Redux Toolkit은 메모이제이션(memoization)이라는 기술을 사용하여 비용이 많이 드는 계산 결과를 캐시하여 성능을 최적화합니다.
  5. 뛰어난 확장성(Greater scalability): Redux Toolkit은 규모 확장적(Scalable)으로 설계되어 대규모 애플리케이션에서 상태를 더 쉽게 관리할 수 있습니다. Context API는 소규모 애플리케이션에는 유용하지만 애플리케이션이 성장함에 따라 다루기 힘들고 관리하기 어려워질 수 있습니다.
  6. 내장 미들웨어(Built-in middleware): Redux Toolkit에는 기능을 확장하고 애플리케이션 동작을 사용자 정의할 수 있는 내장 미들웨어가 함께 제공됩니다.

Context API는 소규모 애플리케이션에 유용할 수 있지만, Redux Toolkit은 상태 관리에 대한 예측이 필요하고, 확장성이 중요하며, 유지관리가 중요한 애플리케이션에 더 효과적입니다. Redux Toolkit을 채택하면 시간이 지남에 따라 유지 관리 및 확장이 더 쉬운 보다 효율적이고 효과적인 React 코드를 작성할 수 있습니다.

Scalability
IT관련 이야기를 할 때 Scalability이라는 단어가 자주 등장합니다. 하지만 Scalabiliy를 단순히 확장성으로 해석하기엔 다소 무리가 있습니다. 오히려 확장성이라는 해석이 어울리는 단어는 Extensible로 볼 수 있습니다. 해석과 관련해서 개인적으로 조사해보고 고민해본 결과 ‘규모 확장성’이라는 해석이 가장 설득력이 있었습니다. Scalability와 관련하여 더 자세히 알고 싶으신 분은 What Does IT Scalability Actually Mean? 를 읽어보시는 것을 추천드리겠습니다.

 

 

마치며

지금까지 Redux와 Context에 대한 비교 분석을 바탕으로 왜 Context가 Redux를 대체할 수 없는지에 대해 알아보았습니다.

 

아무래도 원문의 작성자가 Redux의 maintainer인 관계로 어느정도 Redux 편향적인 아티클인 거 같습니다.

소규모의 프로젝트를 진행하신 분들은 Context API와 Redux Toolkit 중 어느 솔루션을 택할 지에 대해 고민해보신 경험이 있으실겁니다. 저도 Context API를 사용해보며 처음에는 Recoil이나 Zustand같은 최신 상태 관리 라이브러리에 비해선 Old한 방식이지만 Redux랑 비교하면 꽤 쓸만하다는 생각을 했었습니다. 물론 Redux DevTools와 Redux 미들웨어 등을 경험하면서 이에 대한 오해는 현재는 말끔하게 사라졌습니다.

 

이번 포스팅을 작성하면서 Context API 대신 Redux를 사용해야하는 이유만 하나 늘은거 같지만, Redux가 내부적으로는 Context를 사용하기 때문에 Context에 대해 올바르게 이해해야 Redux도 온전히 사용할 수 있다고 생각이 듭니다. 읽어주셔서 감사합니다.

 

 

참고

https://www.linkedin.com/pulse/why-redux-toolkit-better-than-context-api-mhd-firass-barakat

 

Why the redux toolkit is better than Context API :

As React developers, we are constantly looking for ways to make our code more efficient and maintainable. One of the most popular state management solutions in React is Context API, but Redux Toolkit offers several advantages over Context API that can make

www.linkedin.com

https://blog.isquaredsoftware.com/2021/01/context-redux-differences/

 

Blogged Answers: Why React Context is Not a "State Management" Tool (and Why It Doesn't Replace Redux)

Definitive answers and clarification on the purpose and use cases for Context and Redux

blog.isquaredsoftware.com

https://www.linkedin.com/pulse/why-redux-toolkit-better-than-context-api-mhd-firass-barakat

 

Why the redux toolkit is better than Context API :

As React developers, we are constantly looking for ways to make our code more efficient and maintainable. One of the most popular state management solutions in React is Context API, but Redux Toolkit offers several advantages over Context API that can make

www.linkedin.com

 

profile

Lambda

@Lamue

포스팅이 좋았다면 "공감❤️" 또는 "구독👍🏻" 해주세요!