모든 React 개발자는 결국 똑같은 질문에 직면합니다. Context를 사용해야 할까요, 아니면 Redux가 필요한 문제일까요? 개발을 시작한 지 몇 달 되지 않았다면, 온라인상의 수많은 정보 때문에 마치 둘 중 하나를 반드시 선택해야 하는 문제처럼 느껴질 수 있습니다. 어떤 튜토리얼은 Redux를 구식 유산(legacy baggage)처럼 취급합니다. 반면 어떤 이들은 Context가 투두 리스트(to-do list) 수준 이상의 규모로 확장될 수 없다고 경고합니다. 어느 쪽도 도움이 되지 않습니다. 진실은 이 도구들이 서로 다른 종류의 문제를 해결하며, 현명한 선택은 애플리케이션이 실제로 무엇을 하느냐에 달려 있다는 것입니다.

Prop Drilling 문제

상태 관리 전략을 선택하기 전에, 두 도구가 해결하려는 문제가 무엇인지 이해하는 것이 도움이 됩니다. 이커머스 사이트를 구축한다고 가정해 봅시다. 최상위 App 컴포넌트에서 사용자의 프로필을 가져옵니다. 하단에 있는 아주 작은 AccountLink 컴포넌트에서 이 프로필 사진이 필요합니다. 전역 스토어가 없다면, user 객체는 Home, Header, NavContainer, UserDropdown을 거쳐 마지막으로 AccountLink까지 전달되어야 합니다. 그 사이의 모든 레이어가 사용하지도 않는 데이터에 접근하게 됩니다. 이것이 바로 Prop Drilling입니다.

Prop Drilling은 컴포넌트를 취약하게 만듭니다. 중간에 있는 매개체 하나만 제거해도 체인이 끊어지기 때문에 리팩터링이 위험해집니다. 컴포넌트가 단순히 아래로 전달하기만 하는 props를 요구하게 되므로 재사용성도 떨어집니다. Context와 Redux 모두 멀리 떨어진 컴포넌트가 공유 데이터에 직접 구독(subscribe)할 수 있게 함으로써 이 문제를 해결합니다. 하지만 데이터를 전달하는 방식과 그 과정에서 발생하는 비용은 빠르게 갈라집니다.

React Context API가 적합한 경우

React Context는 라이브러리 자체에 내장되어 있습니다. 추가적인 npm 설치도, 빌드 설정도, 보일러플레이트(boilerplate) 파일도 필요 없습니다. 컨텍스트 객체를 생성하고, 트리 일부를 Provider로 감싼 뒤, 중첩된 어떤 컴포넌트에서든 useContext를 사용하여 값을 사용하면 됩니다. 이러한 단순함 덕분에 Context는 상태 변경이 빈번하지 않고 상태의 형태가 비교적 단순한 중소규모 프로젝트에서 빛을 발합니다.

UI 테마를 생각해 보세요. 사용자는 세션당 한 번 정도 라이트 모드와 다크 모드를 전환합니다. 이 값은 모든 styled component로 전파되지만, 변경이 매우 드물기 때문에 성능 문제는 거의 발생하지 않습니다. 인증 상태(Authentication status) 또한 대표적인 사례입니다. 사용자가 로그인하면 isAuthenticated 플래그와 user 객체는 수십 번의 페이지 이동 중에도 안정적으로 유지됩니다. 언어 또는 로컬라이제이션(localization) 설정도 마찬가지입니다. 이것들은 많은 컴포넌트가 필요로 하지만, 변경(mutate)하는 컴포넌트는 거의 없는, 범위가 넓고 변화가 느린 신호들입니다.

문제는 Context가 업데이트를 처리하는 방식입니다. Context Provider의 값이 변경되면, React는 해당 컨텍스트를 사용하는 모든 컴포넌트를 리렌더링합니다. 작은 애플리케이션에서는 이를 느끼지 못할 것입니다. 하지만 규모가 큰 앱에서 빈번하게 변하는 데이터를 널리 사용되는 Context에 담아두면, 불필요한 렌더링이 연쇄적으로 발생하는 현상이 나타납니다. 변동성이 큰 데이터를 격리하기 위해 컨텍스트를 분리할 수도 있지만, 그 시점에는 이미 다른 도구가 이미 해결해 놓은 최적화 작업을 수동으로 설계하고 있는 셈이 됩니다.

Redux Toolkit이 제 역할을 하는 경우

Redux Toolkit은 상태가 복잡하고, 업데이트가 빈번하며, 멀리 떨어진 여러 기능이 충돌 없이 동일한 데이터를 읽고 써야 하는 애플리케이션을 위해 설계되었습니다. 장바구니를 예로 들어 봅시다. 사용자가 상품 카드에서 아이템을 추가합니다. 헤더의 장바구니 아이콘은 배지 숫자를 업데이트해야 합니다. 사이드바가 나타나 품목 목록을 보여줍니다. 할인 코드 입력창은 유효성 검사를 실행합니다. 나중에 결제 페이지에서 장바구니 내용을 읽습니다. 이 상태는 트리 전체에 걸쳐 서로 관련 없는 컴포넌트들에 의해 사용되며, 자주 변경됩니다.

Redux Toolkit은 중앙 집중식 스토어와 명시적인 상태 슬라이스(slices)를 통해 이 문제를 해결합니다. 컴포넌트는 useSelector를 사용하여 자신에게 필요한 데이터의 아주 작은 부분(slivers)에만 구독합니다. 만약 실시간 대시보드에서 주가가 업데이트되더라도, 사용자 프로필 설정을 표시하는 컴포넌트는 깨어나지 않습니다. Redux는 내부적으로 참조 동일성(reference equality) 검사를 사용하여 구독을 세밀하게(granular) 관리합니다. 이는 컴포넌트 수가 수백 개로 늘어날 때 매우 중요해집니다.

또한 Redux는 예측 가능한 데이터 흐름을 제공합니다. 상태 변경은 리듀서(reducers)에 의해 처리되는 디스패치된 액션(dispatched actions)을 통해 일어납니다. 전문 용어처럼 들릴 수 있지만, 실제로 이는 코드베이스에서 addToCart를 검색(grep)하여 장바구니를 수정하는 모든 코드 경로를 찾을 수 있다는 것을 의미합니다. 대규모 팀에서 이러한 규칙은 버그를 방지합니다. 반면 Context는 단순히 값과 세터(setter)일 뿐입니다. 어떤 소비자(consumer)든 setState를 호출할 수 있으며, 잘못된 값의 근원을 추적하려면 여러 컴포넌트에 걸쳐 브레이크포인트를 설정해야 합니다.

두 도구가 실제로 갈라지는 지점

성능 특성은 무엇보다도 이 도구들을 구분 짓는 요소입니다. Context는 모든 소비자에게 무조건적으로 새로운 값을 방송합니다. 반면 Redux는 선택한 슬라이스가 변경된 구독자에게만 알림을 보냅니다. 만약 매초 시세가 갱신되는 실시간 주식 대시보드를 구축한다면, Context는 전역적인 리렌더링 폭풍을 일으킬 것입니다. Redux는 티커 셀과 스파크라인 차트만 다시 계산하도록 할 수 있습니다.

디버깅은 복잡한 앱에서 Redux가 앞서 나가는 또 다른 영역입니다. Redux DevTools는 타임 트래블 디버깅을 제공합니다. 디스패치된 각 액션을 거슬러 올라가며 상태가 되감기는 것을 확인할 수 있습니다. 배송 계산, 결제 검증, 오류 복구가 포함된 다단계 체크아웃 흐름에서 버그를 유발한 정확한 시퀀스를 재현할 수 있다는 것은 매우 가치 있는 일입니다. Context는 표준 React DevTools에 의존합니다. 현재 Context 값을 검사할 수는 있지만, 내장된 액션 로그나 상태 차이(diff) 뷰어는 없습니다. 결국 다시 console.log를 여기저기 뿌리게 됩니다.

미들웨어 및 사이드 이펙트는 Redux의 DNA와 같습니다. Redux Toolkit은 createAsyncThunk를 포함하며 데이터 페칭 라이브러리와 깔끔하게 통합됩니다. API 호출을 조율하고, 로딩 스피너를 보여주고, 네트워크 실패를 처리하며, 결과를 캐싱하는 모든 과정을 Redux 데이터 흐름 내에서 수행할 수 있습니다. Context는 비동기 로직을 위한 내장 패턴을 제공하지 않습니다. 컴포넌트 내부에서 데이터를 가져온 뒤 결과를 Context에 밀어 넣거나, 직접 만든 비동기 유틸리티로 Provider를 감싸야 합니다. 작동은 하지만, 임시방편적인 방식입니다.

설정 비용은 Context가 확실히 승리하는 부분입니다. 테마 Provider를 구축하는 데는 약 5분 정도 걸립니다. Redux Toolkit은 스토어 파일을 생성하고, 슬라이스를 정의하며, 애플리케이션을 Provider로 감싸야 합니다. 예전 Redux와 엄청난 양의 보일러플레이트가 필요했던 시절처럼 일주일씩 걸리는 의식은 아니지만, 여전히 Context보다는 설정할 것이 많습니다. 주말 사이드 프로젝트나 경로가 3개뿐인 대시보드라면, 이러한 오버헤드는 그만한 가치가 없을 수도 있습니다.

한 애플리케이션에서 두 가지 모두 사용하기

한쪽 진영에만 충성을 맹세할 필요는 없습니다. 많은 프로덕션 애플리케이션이 전역 UI 셸 관련 사항에는 Context를, 도메인 중심의 비즈니스 데이터에는 Redux를 사용합니다. 모든 경로에서 필요하고 변경이 드문 테마, 로케일, 그리고 가벼운 인증 플래그 등은 Context에 유지하는 것이 일반적인 패턴입니다. 반면, 빈번한 업데이트와 컴포넌트 간 로직의 정밀한 제어가 필요한 주문 관리 시스템, 알림 센터, 데이터 테이블 등은 Redux에서 관리합니다.

이러한 하이브리드 접근 방식은 정적인 테마 객체에 전체 Redux 스토어를 강제하지 않으면서도, 쉬운 작업은 계속 쉽게 유지할 수 있게 해줍니다. 또한 처음부터 고도화된 상태 관리가 필요하지 않았던 UI 요소들로 인해 Redux 슬라이스가 불필요하게 채워지는 것을 방지합니다.

핵심 요약

더 무거운 도구를 선택한다고 해서 훈장을 받는 것은 아닙니다. 상태가 얼마나 자주 변경되는지, 얼마나 많은 컴포넌트가 상태에 접근하는지, 그리고 팀 경계를 넘어 상태 변경을 추적해야 하는지를 먼저 살펴보세요. 적당한 규모의 앱에서 변경이 느리고 널리 공유되는 값을 관리한다면 Context로도 충분할 것입니다. 상태가 빈번하게 변경되고, 서로 관련 없는 기능들에 걸쳐 있으며, 명확한 감사 추적(audit trail)이 필요하다면 Redux Toolkit이 고생을 덜어줄 것입니다.

컨퍼런스 강연이나 GitHub 스타 수가 아니라, 프로젝트의 형태에 따라 선택하세요. 아이템이 50개에 달하는 장바구니라고 해서 자동으로 Redux가 필요한 것은 아니며, 테마 토글에 전역 스토어가 필요한 것도 아닙니다. 문제에 맞는 도구를 선택하면, 유행이 지나간 후에도 코드베이스를 오랫동안 유지 관리할 수 있을 것입니다.