이 글은 Why React Context is Not a "State Management" Tool 를 읽고 정리한 글 입니다.
소개
모두 상태관리 라이브러리를 사용해 보셨나요?
모두 프로젝트를 생성할 때 Context API와 Redux중에 어떤 상태관리 라이브러리를 선택해서 사용할지 고민했던 기억이 있으실겁니다.
( 저도 예전에 상태관리 라이브러리를 선택하기위해 여러 라이브러리들 중에서 고민했던 기억이 있네요.😉 )
아실수도 있으시겠지만 Context vs Redux라는 전에 리엑트 커뮤니티에서 가장 많이 논의되어온 주제 중 하나입니다.
하지만 안타깝게도 이 논쟁은 도구의 목적과 사용 사례에 대한 혼동에서 생겨나게 됩니다.
오늘 이 글을 통해 Context 와 Redux가 명확하게 어떻게 다른지 이야기해보도록 하겠습니다.
Context와 Redux
먼저 이 도구들의 원래 목적과 방향에 맞게 사용할려면 3가지를 이해하는 것이 중요합니다.
- 이 도구의 목적이 무엇인지
- 어떤 문제를 해결할려고 하는지
- 만들어진 시기와 이유가 무엇인지
또한 현재 해결하고자 하는 문제가 무엇인지 정확히 파악한 후 이 문제를 가장 잘 풀어낼 것 같은 도구를 선택하면 됩니다.
이 도구를 선택한 이유는 누군가 써보라고 해서, 또는 인기가 많다고 따라 쓰는 것이 아니라 이 상황에서 가장 적합한 도구를 찾아 사용하는 것이 중요합니다.
Context?
먼저 리엑트 공식문서를 살펴보도록 하겠습니다.
컨텍스트를 사용하면 구성요소가 정보를 props으로 전달하지 않고 멀리있는 부모로부터 정보를 받을 수 있습니다.
값을"관리"하는 것은 언급하지 않고 값을 "전달" 하는 것만을 언급하고 있습니다.
기존에 레거시 컨텍스트 API는 주로 'sholdComponentUpdate' 함수를 사용해 성능 최적화을 진행했습니다.
하지만 이 기존 방식은 심각한 문제가 있었습니다.
예를 들어 부모 컴포넌트가 업데이트되었지만 자식 컴포넌트가 shouldComponentUpdate로 인해 업데이트를 건너 뛸 경우,
레거시 컨텍스트 API를 통해 전달된 값의 업데이트가 차단되었습니다.
이후 React Context API는 React 16.3에서 새 버전으로 출시되고, 이 문제들이 해결되었습니다.
새로운 Context Api는 중간에 있는 구성 요소가 렌더링을 건너 뛰더라도 값의 업데이트를 하위 구성요소에 즉시 표현합니다.
따라서 렌더링을 건너뛰는 경우에도 Context를 통해 전달된 값의 업데이트가 문제 없이 적용됩니다.
여기서 잠깐! sholdComponentUpdate란?
간단하게 말하면 바뀌어야 하는 부분만 다시 렌더링하도록 해주는 성능 최적화 Lifecycle API다.
Redux?
이번에는 redux를 살펴보도록 합시다. 아까와 마찬가지로 공식문서를 먼저 살펴보도록 하겠습니다.
"Redux is a JS library for predictable and maintainable global state management."
Redux는 예측 가능하고 유지 관리 가능한 전역 상태 관리를 위한 JS 라이브러리입니다.
자, 확실하게 "global state management" 라고 명시하고 있습니다.
Context가 상태관리가 아닌 이유
상태는 애플리케이션의 동작들을 설명하는 모든 데이터를 말합니다.
상태는 서버상태, 커뮤니케이션 상태, 로케이션 상태로 분류할 수 있습니다.
가장 중요한 점은 이 데이터들이 저장되고(stored), 읽고(read), 업데이트되고(updated), 사용된다(used)는 것이죠.
그런 의미에서 상태 관리는 아래 3가지로 이야기 할 수 있습니다.
- 초기 값 저장
- 현재 값 읽기
- 값 업데이트
좋은 상태관리의 예시로 React의 useStatedhk useReducer를 들 수 있습니다.
- 훅을 호출하며 초기값 저장
- 훅을 호출하면서 현재 값 읽기
- setState나 disfetch 함수를 호출해 값 업데이트
- 컴포넌트 리렌더링 시 값 업데이트
이 맥락에서 볼 수 있듯이 Redux또한 확실한 상태 관리 툴 입니다.
- root reducer를 호출하며 초기값을 저장하고, store.getState()로 현재 값을 읽고, store.dispatch(action)으로 값을 업에이트 할 수 있게 하며, store.subscribe (listener)를 통해 스토어의 업데이트를 알립니다.
하지만 React Context는 이 조건에 맞지 않으므로 상태관리 툴이 아닙니다.
Context는 그 자체로는 아무 것도 "저장"하지 못합니다.
<MyContext.povider>를 렌더하는 상위컴포넌트는 Context에 어떤 값을 넣어 줄지만 결정하는 것에만 관여합니다.
실질적인 '상태관리'는 useState,usePeducer에서 훅으로 이루어지는 것입니다.
결론적으로 컨텍스트는 이미 어딘가에 존재하고 있는 상태를 다른 커포넌트와 공유하는 방법일 뿐입니다.
Context와 Redux의 비교
이제 context와 redux의 기능을 비교해 보면서 살펴보도록 합시다.
Context
- 어떤 것도 저장하거나 "관리"하지 않는다.
- React Componenet 에서만 작동한다.
- 타입과 관계 없는 단일 값들을 전달한다. (원시, 객체, 클래스 등등)
- 단일 값을 읽을 수 있도록 한다.
- prop-drilling을 피하기 위해 사용할 수 있다.
- Context가 전달하는 값이 업데이트 되면 Consumer컴포넌트도 자동으로 업데이트 되며, 스킵할 방법이 없다.
- 사이드 이펙트를 처리하는 메커니즘이 전무하고, 오로지 렌더링에만 관여한다.
React + Redux
- 단일 값을 저장하고 관리한다.
- react component 가 아닌 다른 UI와 함께 사용할 수 있다. (React, Vue, Angular, 바닐라 JS 등)
- 단일 값을 읽을 수 있도록 한다.
- prop-drilling을 피하기 위해 사용할 수 있다.
- action을 dispatching하고 reducer를 작동시킴으로 값을 업데이트 한다.
- 사이드 이펙트를 발생시키기 위한 미들웨어를 사용할 수 있다,
- 컴포넌트가 store의 업데이트를 구독하여 해당값이 바뀌었을 때만 컴포넌트가 리렌더링 되도록 할 수 있다.
보다시피 이 둘은 매우 다른 도구들 입니다.
하나의 공통점을 찾으라면 prop-driling을 피하게 해준다는 것 정도겠네요.
Context와 useReducer
처음에 이야기했던 Context vs Redux 에서 가장 큰 문제점은, "사람들이 useReducer로 상태관리를 하고, Context로 값을 전달해준다."라고 말하는 대신 "나 Context 사용했어." 라고 말하며 발생합니다. 이때문에 많은 사람들이 Context로 상태를 "관리" 한다는 이야기를 한다고 봅니다.
그럼 한번 Context + useReducer의 조합을 살펴봅시다.
이 조합은 Redux + React-Redux와 정말 상당한 부분을 닮아 있습니다.
공통점
- 저장(보관)되는 값
- 리듀서 함수
- 액션 디스패칭
- 중첩 컴포넌트 내에서 값을 전달하고 읽는 기능
차이점
- Context + useReducer는 리액트의 기능이므로 리액트에서만 사용할 수 있다. Redux 스토어는 모든 UI에 독립적이므로 별개로 사용 가능하다.
- 리액트 DevTools로 현재 컨텍스트 값을 볼 수는 있지만, 시간 순으로 지난 기록을 볼 수는 없다.
리덕스 DevTools로는 디스패치된 모든 액션, 모든 액션의 컨텐츠, 액션 실행 후의 상태, 시간에 따른 상태의 차이를 모두 볼 수 있다. - useReducer에는 미들웨어가 없다.
useReducer와 useEffect를 함께 사용하여 사이드 이펙트를 낼 수 있고, 미들웨어와 비슷한 대안을 사용하려는 시도도 있어왔으나 기능과 효과면에서 리덕스 미들웨어에 비해서는 한계를 지닌다.
이처럼 얼핏 보기에는 두 조합이 비슷해보이지만, 절대 똑같지 않습니다. (실제로 Redux를 대체할 수 없습니다.)