전역 상태는 편리할 수 있지만, 프로그래밍 관점에서 피해야할 '달콤한' 요소입니다. 전역 상태가 어디서든 수정 가능하다면, 프로그램의 어느 곳에서 상태를 변경할 수 있는 가능성이 높아져 복잡성이 증가할 수 있습니다. 이러한 이유로, 우리는 Context를 활용하여 상태를 지역적으로 관리하면서도 편리하게 사용할 수 있는 패턴을 사용합니다. 이를 통해 코드의 가독성과 유지보수성을 향상시키고, 상태 변경에 따른 부작용을 줄이며 안정성을 유지할 수 있습니다.
SomeComponent에서 Count 컨텍스트를 사용하려면, 해당 컨텍스트를 활용할 수 있도록 부모 요소에 Provider를 통해 컨텍스트를 전달해야 합니다. 이를 통해 SomeComponent 내부에서 해당 컨텍스트를 활용할 수 있는 상태가 되는 것입니다.
왜 Context 를 그대로 사용하지 않고 use-context-selector 라이브러리와 함께 사용하나요?
context 또한 제한적인 측면이 존재하기 때문입니다. 특히 context를 사용하는 컴포넌트는 해당 값이 변경될 때마다 불필요하게 다시 렌더링(re-rendering)되는 문제가 발생할 수 있습니다. 이는 원시 값이라면 괜찮을 수 있지만, 객체 안에 있는 특정 값만 필요한 경우에는 불필요한 렌더링을 초래할 수 있습니다.
이런 문제를 해결하기 위해 use-context-selector 라이브러리가 등장했습니다. 이 라이브러리는 구독 패턴을 활용하여, selector가 반환하는 값이 변경될 때에만 컴포넌트를 다시 렌더링합니다. 이로써 렌더링 효율을 향상시킬 수 있습니다. 사용할 때에 중요한 것은, 위의 예시처럼필요한 값을 가져오는 selector를 라이브러리에 전달해주는 것입니다.
Context를 사용하기 전에 고려해야 할 점
Context는 편리한 동시에 데이터 흐름을 이해하기 어렵게 만들 수 있는 단점이 있습니다. 데이터를 내려주기 위해 Properties를 사용하는 것은 조금 복잡할 수 있지만, 그로 인해 데이터 흐름이 명확하게 드러나는 장점이 있습니다.
만약 state를 공유하는 것이 복잡하거나 prop-drilling 때문에 개발이 어려운 상황이라면, 다음과 같은 대안을 먼저 고려해볼 수 있습니다.
컴포넌트 합성
복잡한 상태를 관리하기 위해 컴포넌트를 조합하고 중첩함으로써, 각 컴포넌트는 자체적인 상태를 가지고 관리할 수 있도록 합니다. 이렇게 하면 상태 관리가 더욱 명확해질 수 있습니다.
상태 지역화
필요한 상태를 가능한 한 해당 컴포넌트 내부로 지역화하여 관리합니다. 이렇게 하면 컴포넌트 간의 상태 공유가 줄어들어 데이터 흐름을 더 명확하게 만들어줍니다.
2. useReducer 로 상태 관리하기
Context는 상태뿐만 아니라 함수도 공유할 수 있는데, 이는 복잡한 상태 관리 로직과 라우터 이동(router.push)과 같은 함수들이 함께 작용할 수 있는 환경을 제공합니다. 하지만 상태와 다른 행동을 하는 로직이 섞이면 코드가 복잡해질 수 있습니다. 이런 경우에는 '관심사 분리'가 필요합니다.
useReducer는 상태 관련 로직을 이전 상태와 페이로드(payload)를 받아 다음 상태를 반환하는 순수 함수입니다. 이로써 컴포넌트 외부에서도 작성할 수 있고, 하나의 액션에서 여러 상태를 조회하고 동시에 수정하는 것이 가능합니다. 이로 인해 상태 관리의 관심사를 분리하고 코드를 단순화할 수 있습니다.
이는 모든 면에서 유리한 것만이 아닙니다. 실제로 작성하는 코드의 양은 useState보다 늘어날 수 있어, 간단한 상태에는 오히려 복잡도가 증가할 수 있습니다. 따라서, 상태가 커지고 복잡해지며 관심사 분리가 필요한 상황에서만 이를 적용하는 것을 권장합니다.
reducers 객체의 각 키 값은 dispatch의 타입(type)으로 사용되며, 해당 키에 해당하는 reducer 함수의 두 번째 매개변수는 payload가 됩니다. 일반적으로 reducer를 사용하려면 원하는 값을 수정한 후 새로운 state를 반환해야 하는데, 이 때 객체 복사와 같은 복잡한 작업이 필요했습니다. 그러나 createSlice 내부에서 Immer 라이브러리를 사용하여 간단하게 할당만으로도 state를 수정할 수 있습니다. 이를 통해 코드를 더 간결하게 작성할 수 있습니다.
따라서 createSlice는 RTK와 비슷한 스타일로 리듀서를 생성하고 관리할 수 있도록 도와주는 함수입니다. 이는 기존 Redux 개발 경험을 보다 편리하게 만들어주며, Immer 라이브러리의 도움으로 불변성 유지를 더욱 간단하게 처리할 수 있게 해줍니다.