📗

2.3 Redux 특징 및 한계점

2.3.1 리덕스 개발자 도구 (Redux DevTools)

1) 리덕스 개발자 도구의 역할

Redux는 애플리케이션에서 상태 관리의 표준으로 자리 잡은 라이브러리로, 믿을 수 있는 데이터 흐름과 효율적인 상태 관리를 제공합니다. 그러나 이러한 강점과 함께, Redux의 복잡성과 기능의 방대함은 사용자에게 도전적일 수 있습니다. Redux의 효과적인 활용을 위해 개발자는 애플리케이션의 데이터 흐름과 상태 변화를 정확하게 이해하고 파악해야 합니다.
이러한 상황에서 리덕스 개발자 도구는 빛을 발합니다. Redux의 특징과 한계점을 이해하고, 복잡한 애플리케이션 상태를 분석하며 해결책을 찾는 데 필수적입니다.
 
복잡한 상태 관리를 간편하게 만들어 주는 강력한 도구인 리덕스 개발자 도구는 애플리케이션의 실시간 상태 변화를 모니터링하고 액션 흐름을 직관적으로 이해할 수 있도록 돕는 실용적인 기능을 제공합니다.
  • 상태 추적 : 애플리케이션의 상태를 실시간으로 추적하고, 상태 변경 내역을 시각적으로 볼 수 있기 때문에 어떤 액션으로 인해 상태가 어떻게 변경되었는지 쉽게 확인 할 수 있고 디스패치 한 시간들과 액션의 타입, 페이로드 및 상태 변화 등의 세부 정보를 확인 할 수 있습니다.
  • 디버깅 : 상태가 새로 바뀔 때마다 상태 객체의 모든 버전을 기록하여 이전 버전으로 언제든 돌아갈 수 있으며, 그로 인해 문제 파악이 용이하여 버그를 찾고 디버깅 하는데 도움을 줍니다.
  • 액션 로깅 : 애플리케이션에서 발생한 모든 액션들을 자세하게 로깅하고 기록하여 애플리케이션의 동작을 이해하고 분석 할 수 있습니다.
  • 액션의 테스팅과 검증 : 액션을 직접 디스패치하고 애플리케이션의 반응을 확인할 수 있어, 액션들이 예상대로 동작하는지 테스트하고 검증할 수 있습니다.
  • 성능 최적화 : 애플리케이션의 성능을 평가하고 최적화할 수 있는 기능을 제공합니다. 액션의 수, 상태의 규모 등을 분석하여 성능 문제를 찾고 해결할 수 있습니다.
  • 개발 생산성 향상 : 개발자는 코드 변경 후에도 신속하게 결과를 확인하고 디버깅 할 수 있습니다. 이는 개발 생산성을 향상시키고 빠른 개발 주기를 가능하게 합니다.
 

2) 설치 및 설정

브라우저에서 Redux DevTools를 사용하기 위해 브라우저 확장 프로그램을 설치할 수 있으며 크롬, 파이어폭스, 사파리, 엣지 등 다양한 브라우저에서 사용할 수 있습니다.
  • Google Chrome 또는 Microsoft Edge : Redux DevTools Extension을 Chrome 웹 스토어에서 설치합니다.
  • Mozilla Firefox : Redux DevTools Extension을 Firefox 애드온 스토어에서 설치합니다.
 
확장프로그램이 준비 되었다면 다음으로 Redux DevTools를 프로젝트에 연결하기 위해서 리덕스 스토어를 생성할 때 Redux DevTools 확장을 사용하도록 설정해야 합니다.
import { createStore } from 'redux'; import rootReducer from './reducers'; const store = createStore( rootReducer, window.**REDUX_DEVTOOLS_EXTENSION** && window.**REDUX_DEVTOOLS_EXTENSION**() );
 
위 코드에서 window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 부분은 Redux DevTools를 사용하기 위한 코드입니다. 이 코드를 createStore 함수의 두 번째 인자로 넘겨주면 Redux DevTools와 연결됩니다.
 
프로젝트를 실행하고 브라우저 우측 상단의 확장 프로그램목록에서 Redux DevTools를 실행 시켜줍니다.
notion imagenotion image
위 화면은 Redux DevTools가 정상적으로 실행된 화면입니다.
이렇게 브라우저 확장 프로그램을 설치하고 코드에 연결하는 것만으로 Redux DevTools를 활용할 수 있습니다.
 

3) 기능 소개 및 활용

Redux DevTools는 Redux 애플리케이션을 개발하고 디버깅하는데 사용되는 도구로 여러 섹션을 제공합니다. 각 섹션은 애플리케이션의 상태, 액션 그리고 상태 변화의 시각적 표현 등을 보여주는 데 사용됩니다. 각 섹션에 대한 설명과 활용 방법은 다음과 같습니다.
(+ 버튼을 누르면 숫자 1을 INCRESE하고, - 버튼을 누르면 숫자가 1을 DECRESE하는 리액트 리덕스를 사용한 프로그램을 활용 예시로 사용하였습니다.)
 

(1) Dispatch

Dispatch 섹션은 Redux 애플리케이션에서 dispatch 된 액션들의 기록을 보여주며, 각 액션의 타입, 페이로드, 디스패치 시간 등의 세부 정보를 제공하여 액션의 흐름을 모니터링하고 디버깅하는 데 사용됩니다. 이를 통해 액션 디스패치의 순서와 내용을 시각적으로 확인할 수 있어 애플리케이션의 동작을 이해하고 문제를 해결하는 데 도움을 줍니다.
notion imagenotion image
 

(2) Action

Action 섹션은 Redux 애플리케이션에서 디스패치 된 액션들의 기록을 보여주는 곳입니다. 이 섹션은 액션들의 디스패치 순서와 각 액션의 세부 정보를 보여줍니다. 각 액션은 타입, 페이로드, 디스패치 된 시간 등의 정보를 포함하고 있어, 애플리케이션의 상태 변화를 추적하고 디버깅하는 데 사용되고 액션 흐름을 시각적으로 이해하고, 액션이 어떻게 상태를 변경하는지 확인할 수 있어 디버깅과 성능 최적화에 유용합니다.
notion imagenotion image
 

(3) State

State 섹션은 Redux 스토어의 현재 상태를 표시해 주는 곳입니다. 이 섹션은 애플리케이션의 상태 객체를 보여주며, 상태가 어떻게 구성되어 있는지, 각 속성들의 값은 무엇인지 등을 상세하게 확인할 수 있습니다. 이를 통해 개발자는 애플리케이션의 상태를 시각적으로 이해할 수 있고, 특정 시점에서의 상태를 확인하여 디버깅할 수 있습니다. 이는 애플리케이션의 상태 변화를 추적하고 문제를 해결하는 데 큰 도움을 주며 Redux의 불변성과 상태 변화를 이해하고, 문제를 해결하는 데 중요한 정보를 제공합니다.
notion imagenotion image
 

(4) Diff

Diff 섹션에서는 두 상태 간의 차이점을 비교하여 다른 속성들이 어떻게 변경되었는지를 보여 줍니다. 예를 들어, 어떤 액션에 의해 특정 객체의 특정 속성이 변경되었을 때, 그 차이를 시각적으로 확인할 수 있습니다. 이를 통해 개발자는 액션에 따른 상태의 변화를 이해하고, 문제 해결을 위해 애플리케이션의 상태를 더 정확하게 분석할 수 있습니다.
notion imagenotion image
 

(5) 시간 여행 디버깅

아래 예시 이미지의 디스패치에서 세 번째 액션인 counter/DECREASE의 발생 시점의 상태 스냅샷을 확인하여 counter가 2에서 1로 변경 된 것을 확인 할 수 있는데, 이처럼 시간 여행 디버깅 기능은 상태 변화와 액션 흐름을 시각적으로 추적하여 어떤 액션이 어떤 상태 변경을 일으켰는지 정확하게 파악하고 복잡한 애플리케이션에서 특정 상태 문제를 찾고 해결 하는데 매우 유용합니다.
notion imagenotion image
이러한 리덕스 개발자 도구의 기능들을 적절히 활용하면 코드의 흐름과 상태의 변화를 명확하게 시각화하고 이해할 수 있게 되며 그로 인해, 애플리케이션의 복잡성을 능률적으로 처리하고 개발 및 디버깅 작업 또한 훨씬 효과적으로 수행할 수 있도록 만들어 줄 것입니다.
 

2.3.2 Redux에서 불변성

Redux에서 불변성(immutability)은 상태 관리의 핵심 원칙 중 하나입니다. 이는 상태를 변경할 때 기존 상태 객체를 직접 수정하지 않고, 새로운 상태 객체를 생성하여 변경사항을 반영하는 것을 의미합니다. 이를 위해 JavaScript의 스프레드 연산자나 Object.assign() 등을 사용하여 새로운 객체를 생성하고 변경사항을 반영합니다.
 
다음은 Redux에서 불변성을 지키면서 state를 변경하는 간단한 예제 코드입니다.
const initialState = { counter: 0, todos: [] }; function Reducer(state = initialState, action) { switch (action.type) { case 'INCREMENT': return { ...state, counter: state.counter + 1 }; case 'ADD_TODO': return { ...state, todos: [...state.todos, action.payload] }; default: return state; } }
위의 코드에서 INCREMENT 액션은 counter 상태 값을 증가시키고, ADD_TODO 액션은 새로운 할 일 항목을 추가합니다. 여기서 주목해야 할 점은 state 객체를 직접 수정하지 않고 새로운 객체를 반환한다는 것입니다.
{ ...state, counter: state.counter + 1 }  이 코드는 기존의 state 객체를 복사한 후 counter 속성만 변경하여 새로운 객체를 반환합니다. { ...state, todos: [...state.todos, action.payload] } 이 코드는 기존의 todos 배열에 새로운 요소 (action.payload) 를 추가하여 그 결과를 배열과 함께 새로운 상태 객체를 반환합니다. 이런 방식으로 Redux에서는 불변성을 유지하면서 상태 변경 작업을 수행합니다.
 
그렇다면 Redux에서 상태를 변경할 때 불변성을 지켜 주어야 하는 이유는 무엇일까요?
 
가장 먼저 예측 가능한 상태 변경이 가능하다는 것을 장점으로 꼽아볼 수 있겠습니다. 불변성을 유지하면 어떤 액션이 상태에 어떤 변화를 일으키는지 명확한 파악과 이해가 가능합니다. 마찬가지로 상태가 어떻게 변화하는지 추적하기도 쉽겠죠. 이는 디버깅을 더욱 효과적으로 할 수 있도록 도와줍니다. 실제로 Redux DevTools 도구에서 제공하는 시간 여행 디버깅은 애플리케이션의 이전 버전의 상태들을 저장해 두고, 그중 하나로 돌아갈 수 있게 해줍니다.
 
예를 들어 사용자가 UI에서 특정 액션을 발생시켰으나 예상치 못한 결과가 나타난 경우, 개발자는 시간 여행 디버깅 기능을 활용하여 문제가 발생하기 직전의 상태로 복구해 낼 수 있습니다. 그런 다음 해당 액션에 의해 일어난 정확한 state 변화 과정을 확인하여 문제 원인을 파악합니다. 하지만 이러한 기능은 state가 불변성 원칙에 따라 관리되었을 때만 가능합니다. 만약 state가 직접 변경되었다면, 이전 상태들을 정확하게 추적하는 것이 불가능해집니다.
 
상태 추적뿐만 아니라, 성능 최적화에도 불변성 유지가 도움이 됩니다. JavaScript에서 객체 비교는 주소값 기반으로 이루어집니다. 즉, 두 객체가 같은 참조를 가리키고 있다면 동일한 것으로 판단합니다. 따라서 불변성을 지키기 위해 새로운 객체나 배열을 생성하면, 그것은 새로운 참조를 가리킬 것이므로 React는 이를 통해 상태가 변경되어 해당 컴포넌트를 리렌더링을 해야 한다는 사실을 쉽게 판단할 수 있습니다. 객체 깊숙이 들어가 하나하나 비교하여 결론 내릴 필요 없이 간단히 주소값을 비교하는 방식으로 동작하기 때문에 좋은 성능을 유지할 수 있습니다.
 

2.3.3 Redux의 장단점

리덕스는 상태 관리에 유용한 라이브러리입니다. 다른 상태 관리 라이브러리들과 비교했을 때, 리덕스는 지금까지도 많은 개발자들 사이에서 사용되고 있습니다.
 
notion imagenotion image
위 사진은 과거부터 현재까지 상태 관리 라이브러리들의 다운로드 수를 의미합니다. 타 라이브러리에 비해 리덕스가 압도적으로 많이 사용되고 있다는 걸 알 수 있지만, 많이 사용된다고 해서 리덕스가 완벽한 상태 관리 라이브러리라는 의미는 아닙니다. 그렇다면 어떤 장점이 있어 오랜 시간이 지난 지금까지도 많은 개발자들이 리덕스를 사용하는지, 또 리덕스를 사용했을 때 어떤 문제점이 있는지 한번 살펴보겠습니다.

1) 리덕스의 장점

(1) 상태의 중앙화

‘중앙화’ 라는 것은 애플리케이션의 상태나 데이터를 단일 중앙 저장소에서 관리하는 것을 의미합니다. 리액트에서 props를 사용하여 컴포넌트 간 데이터를 전달하다 보면 컴포넌트 구조가 깊어지게 되고 중간 단계의 컴포넌트에서는 데이터를 전달할 필요가 없음에도 불구하고 불필요하게 props를 받게 될 수 있습니다. 이로 인해 데이터 전달과 관련된 코드가 복잡해지고 유지보수가 어려워질 수 있습니다. 하지만 리덕스는 중앙화된 상태 저장소를 통해 데이터가 관리되기 때문에 컴포넌트 간 데이터를 직접 전달하지 않고 데이터가 필요한 컴포넌트에서 곧바로 데이터를 사용할 수 있습니다. 따라서 컴포넌트 간에 데이터를 쉽게 공유할 수 있고, 상태가 한 곳에서 관리되기 때문에 상태의 변경이나 업데이트를 쉽게 예측할 수 있습니다.
notion imagenotion image
 

(2) 다양한 기능 확장

이 내용은 사실 장점이자 단점일 수 있는 부분입니다. 장점인 부분만 다뤄본다면 리덕스는 다른 라이브러리와 함께 통합하여 사용할 수 있습니다. 이러면 리덕스를 사용하는 애플리케이션에서 더 많은 기능을 사용할 수 있도록 확장할 수 있습니다. 리덕스와 통합하여 사용할 수 있는 라이브러리에는 여러 가지가 있지만, 대표적으로 미들웨어가 있습니다. 미들웨어를 활용하여 액션을 디스패치할 때 추가적인 작업을 수행하도록 할 수 있는데 이를 통해 비동기적 작업 처리, 로그 기록, 상태 변경 추적 등 단순 상태 관리를 넘어 다양한 기능을 사용할 수 있습니다.
 

2) 리덕스의 단점

(1) 복잡한 초기설정과 보일러 플레이트

리덕스를 사용하기 위해서는 액션, 액션 생성 함수, 리듀서, 스토어 등 여러 가지 요소들을 작성해야 합니다. 아주 간단한 애플리케이션도 리덕스를 도입하게 되면 작성해야 할 요소들이 많기 때문에 자연스레 코드의 양이 증가하게 됩니다. 이로 인해 반복적이고 일반적인 작업을 수행하는 코드를 의미하는 “보일러 플레이트 코드”가 발생하게 됩니다. 따라서 이런 문제들이 프로젝트 구조의 복잡성을 높이는 원인이 됩니다.
 

(2) 높은 자유도와 러닝 커브

리덕스는 자체적인 기능뿐만 아니라 비동기 작업 처리, 액션 및 상태 변경 추적, 불변성 유지, 브라우저 저장소에 상태 저장 등 다양한 기능들을 제공하는 라이브러리들과 함께 사용할 수 있습니다. 이러한 라이브러리들은 기존 리덕스의 기능을 확장시키고 더욱 효율적으로 다룰 수 있도록 도와줍니다. 하지만 이는 리덕스의 단점으로 다가올 수 있습니다. 리덕스에는 어떤 라이브러리와 함께 사용해야 한다는 명확한 가이드나 규정이 없기 때문에 많은 선택지로 하여금 개발자들에게 혼란을 줄 수 있고, 해당 기능을 사용하기 위해 라이브러리를 설치하게 되면 해당 라이브러리에 대한 추가적인 학습을 요구하기 때문에 러닝 커브가 높아질 수 있습니다.
notion imagenotion image
 

2.3.4 리덕스 사용 시 고려 사항

앞에서 리덕스를 사용했을 때 얻을 수 있는 이점과 발생할 수 있는 문제에 대해 알아보았습니다. 분명 리덕스는 많은 개발자들이 사용하고 여러 프로젝트에서 사용되는 상태 관리 라이브러리임에 틀림없지만 사실 리덕스가 모든 상황에 적절하게 사용할 수 있는 라이브러리는 아닙니다. 그래서 리덕스를 사용하기 전에, 다음 몇 가지 사항들을 고려해 보시길 바랍니다.

1) 어떤 문제를 해결하려고 하는지

진행하고 있는 프로젝트나 애플리케이션에서 어떤 문제를 해결하려고 하는지 이해하는게 중요합니다. 상태 관리의 필요성을 판단하고, 다음 질문들에 대해 고민해 보시길 바랍니다.
  • 상태가 여러 컴포넌트간 공유되어야 하는데 이를 어떻게 처리하고 있는가?
  • API 호출과 같은 비동기 작업을 어떻게 처리하고 있는가?
  • 애플리케이션 내의 데이터가 어떻게 관리되고 업데이트되는지 이해하고 있는가?
물론, 프로젝트 내의 문제에 따라 다른 질문을 던져볼 수 있습니다. 리덕스는 주로 상태 관리와 관련된 문제를 해결하기 위해 사용되는 도구입니다. 따라서 프로젝트 내에 어떤 문제가 있고, 이 문제를 해결하기 위해 리덕스가 어떤 도움을 줄 수 있는지 생각해 보시길 바랍니다.
 

2) 문제 해결을 위해 리덕스가 아닌 다른 방법이 있는지

만약 리덕스로 처리해야 하는 작업이 서버에 있는 데이터를 가져오고 캐싱 처리를 위한 작업이라면 리덕스보다 이러한 작업에 더 특화되어 있는 React-Query를 사용하는 게 더 좋은 방법일 수 있습니다. 리덕스는 주로 상태의 중앙화된 관리와 복잡한 애플리케이션 상태 관리에 사용되지만, 단순한 상태 공유나 컴포넌트 트리를 통한 상태 전달이 목표라면 리액트에서 기본적으로 제공하는 Context를 사용하는 방법이 더 적절할 수 있습니다. 이렇게 해결하고자 하는 문제를 더 좋은 방법으로 해결할 수 있는지에 대해 한번 더 생각해 본다면 프로젝트의 복잡성을 최소화하고 성능을 향상시킬 수 있습니다.
 
지금까지 리덕스를 사용할 때 어떠한 부분들을 고려해야 하는지에 대해 알아보았습니다. 리덕스는 다른 라이브러리에 비해 러닝 커브가 높은 편에 속합니다. 따라서 상태 관리 라이브러리를 선택할 때는 많은 개발자들이 사용하고, 인기 있는 라이브러리를 사용하는 게 아니라 전체적인 흐름을 생각해 보고 나의 프로젝트에 리덕스가 적절한지 판단해 보시길 바랍니다.