Redux 동작 원리
1. Redux Flow
우선 앞에서 배운 React와 Redux의 개념을 한 번 정리하고 가도록 하겠습니다.
- React는 UI 컴포넌트를 사용자 정의 요소로 표현하기 위한 JS 라이브러리.
- Redux는 데이터를 단순하고, 엄격하게 관리함으로서 앱을 예측 가능하게 만들기 위한 JS 라이브러리.
- 공통점 : 복잡한 프로젝트에서 복잡도를 낮춰줌
1.1 Redux의 흐름
해당 코드는 코드로 복사해서 live mermaid에서 확대해서 보는 것을 권합니다.
Mermaid Live Editor[현재 예제에서의 흐름]
[Redux 내부 상태에서 호출 흐름]
1.2 비유를 통한 예시
아래와 같이 비유를 해보도록 하겠습니다.
- 손님: 프로그래머와 유저
- 점원: Redux에 기본 메서드들

- store 만들기 : 카페에 바리스타(reducer)가 없으면 안 됩니다. 스토어에는 저장하고 싶은 사용자의 상태를 저장합니다.
createStore함수를 사용하여 만들 수 있으며, 한 개의 프로젝트 당 하나의 store만 가질 수 있습니다.어떤 컴포넌트에서든지 변화가 일어날 데이터(상태값)는 모두 하나의 스토어에 넣습니다. 데이터를 한 곳에 모아놓기 때문에 에러가 발생했을 때 각각의 컴포넌트에 가서 값을 확인할 필요없이 데이터가 저장되어 있는 store에 가서 확인하면 됩니다. 스토어만 관리하면 되는 것이죠.const store = Redux.createStore(reducer);
- reducer 만들기 : 바리스타를 통하지 않고 고객이(프로그래머가) 직접 커피를(데이터) 만들(조작할) 수 없습니다. 데이터를 보다 안전하게 관리하기 위해서입니다.
state는 reducer를 통해서만 값을 처리할 수 있습니다. reducer는 전달된 액션(action)과 이전 state값을 가지고 어떻게 값을 처리해줘야할지 결정합니다. 실제 값의 변경이 일어나서 reducer가 호출되면 액션(action)에 따라서 값이 바뀌게 되고 새로운 state값을 반환합니다. 예를 들어 아래의 코드에서는 “ADD”라는function reducer(state, action) { // 커피 제조 }action.type이 reducer에게 전달되었을 때, ADD에 해당하는 값을 수정한 뒤 반환합니다. 만약 액션이 “ADD”와 “DELETE”가 아니라면 기존 상태 값을 반환합니다.reducer의 첫번째 매개변수인 state는 처음 호출될 때,const reducer = (state = 0, action) => { switch (action.type) { case 'ADD': return state + 1; case 'DELETE': return state - 1; default: return state; } };undefined가 됩니다. 그래서 초기값을 지정해줘야합니다.
- render 만들기(서빙 점원) : 실제 화면에 뿌려주는 역할을 합니다. 이때 서빙 점원은
var state = store.getState();를 통해 데이터를 받아야 합니다.function red() { var state = store.getState(); // render(innerHTML로 구현) } function blue() { var state = store.getState(); // render(innerHTML로 구현) } function green() { var state = store.getState(); // render(innerHTML로 구현) }
-
subscribe : 굳이 비유를 하자면, 진동벨입니다. 주문이 완료되면 진동벨이 울리고 점원을 통해 구독했던 모든 컴포넌트의 state값이 교체됩니다. 즉, 새로운 데이터가 생성될 때마다 화면을 갱신하는 것입니다.
function red() { ... 중략 ... } store.subscribe(red); // 이 red는 구독한 함수, 값 X function blue() { ... 중략 ... } store.subscribe(blue); function green() { ... 중략 ... } store.subscribe(green);subscribe함수는 액션에 의해 상태가 업데이트 될 때마다 실행됩니다.
-
action과 dispatch : 주문서로(action) 점원에게 주문을(dispatch) 하면 바리스타(reducer)에게 주문서를 넘기는 역할을 합니다.
function red() { // store.dispatch({type:'CHANGE_COLOR', color:'red'}); }액션 객체는 type 필드를 반드시 가지고 있어야 합니다. reducer 함수가 이 type 필드값과 이전 state값을 참고해서 새로운 state를 만들기 때문입니다.
// Example 1 { type: "ADD", id : 1, } // Example 2 { type : "ADD", data : { id : 1, text : 'Have a lunch' } }이렇게 매번 객체를 전달해주는 것은 여간 번거로운 일이 아닐 것입니다. 그래서 객체로 만들어주는 액션 생성 함수를 만들어 사용합니다. 이는 실수를 줄여 보다 견고한 코드를 작성할 수 있게 만듭니다.
const addNumber = () => { return { type: 'ADD' }; };디스패치는 스토어의 내장 함수 중 하나로
dispatch를 통해 reducer 함수를 동작시킵니다. reducer 함수에게 state값과 action을 넘겨주려면dispatch를 사용하여 넘겨주면 됩니다. 파라미터로는 액션 객체를 넣어줍니다. 이때 액션 객체를 직접 선언하는 대신 기존에 만든 액션 생성 함수(액션 객체를 반환하는 함수)를 넣어서 실행시켜도 됩니다.store.dispatch(addNumber); // store.dispatch({ type: "ADD" })
- (getState) 커피(데이터)를 가져오는 점원!
getState를 사용하면 store안에 있는 현재의 state값을 가져올 수 있습니다.subscribe함수와 함께 사용하면 업데이트된 state값을 확인할 수 있습니다.(subscribe는 상태가 업데이트 될 때 실행됩니다.)store.getState(); - (action → dispatch → reducer → state변경 → subscribe → render → getState → state) : 비유로는 설명이 어려워 해당 프로세스는 code의 flow대로 설명하도록 하겠습니다.
- dispatch가 일어납니다.
- subscribe으로 해당 action이 들어옵니다.
- state를 수정합니다.
- subscribe을 통하여 값이 subscribe에 등록된 모든 요소에 state가 수정되었음을 전파합니다.
- render에서 getState를 통해 값을 새로 받아옵니다.
- 다시 render합니다.
replaceReducer()
리듀서를 변경할 때 사용되며 잘 사용하지 않는 함수입니다. 하나의 스토어에 반드시 하나의 리듀서만 있어야 하는것은 아니며, 리듀서를 '변경할 수도 있다.' 정도로만 캐치하고 넘어가 주시기 바랍니다.
2. 데이터는 어떻게 변경될까요?
React에서 상태 값을 바꿀 때는 useState가 제공하는 setState를 사용하였습니다. 하지만 앞으로 사용할 redux에서는 state 자체에 접근하는 것도 직접 수정하는 것도 불가능합니다. 대신 Reducer 함수에게 수정을 요청합니다.

const reducer = (state = 0, action) => {
switch (
action.type // action.type이 "PLUS"라면 state 값을 1 더할 것입니다.
) {
case 'PLUS':
return state + 1;
case 'MINUS': // action.type이 "MINUS"라면 state 값을 1 뺼 것입니다.
return state - 1;
default:
return state; // 기존 state 반환.
// action.type이 "PLUS, MINUS" 모두 아니라면 state 값의 변화는 없습니다.
}
};
3. Redux를 사용하는 이유
상태 값을 전역으로 관리해주는 useContext와의 차이점이 무엇일까요? useContext도 불필요한 props 전달을 막고 전역으로 값들을 관리해주는데 말이죠.
좋은 아티클이 있어 공유합니다. 아래 내용을 참고해 주세요.
Context API vs ReduxReact useContext는 상태를 관리하지 않습니다. 상태는 Context의 값을 꺼내서 사용하는 useState가 관리하죠. 또한 useContext의 목적은 React의 props-drilling을 피하는 것입니다. 하지만 프로젝트가 클수록 관리해야 할 값들은 많아지고 Provider를 더 많이 사용하게 되면서 provider안에 provider로 깊은 중첩 관계가 될 수 있습니다. 반면에 리덕스는 데이터를 저장함과 동시에 상태를 관리하며, 단일한 저장소를 사용하기 때문에 여러개의 store가 중첩되는 경우도 없습니다.
<AuthContextProvider>
<UIContextProvider>
. . .
<UserForm />. . .
</UIContextProvider>
</AuthContextProvider>
그리고 Redux는 React와는 다른 라이브러리입니다. Vue에서도 사용할 수 있고 순수한 JS에서도 사용할 수 있습니다. 어떠한 프레임워크 환경에서 개발하든 JS를 사용하는 프로젝트라면 거대한 규모의 프로젝트 상태 관리를 보다 손쉽게 관리하도록 도와줍니다.
아래 영상에서 상태 관리가 복잡해질 경우 얼마나 많은 복잡도가 향상되는지 잘 설명하고 있습니다.
Redux가 좋은 가장 중요한 이유 - 생활코딩변수의 수가 컨트롤 가능한 정도의 복잡하지 않은 프로젝트에서는 React에 내장되어 있는 Context를 사용하는 것이 좋을 수 있습니다. 프로젝트에서의 리덕스 교육 비용과 효용을 잘 저울질할 필요가 있습니다.
또한, Context에서는 가지고 있는 state가 하나만 변경되어도 Context의 값을 가지고 있는 모든 컴포넌트가 렌더링이 되어버립니다.
예를 들어, 아래와 같은 컨텍스트와 컴포넌트들이 있다고 가정해 봅시다.
context = {a:1, b:2, c:3}
A 컴포넌트 : context에서 b와 c 사용
B 컴포넌트 : context에서 a 사용
여기서 a를 사용하고 있는 B 컴포넌트에서 a를 변경하면, A 컴포넌트는 b와 c만 사용하고 있음에도 리렌더링이 되어버립니다. 이런 상황처럼 컨텍스트를 사용하면 바뀔 필요가 전혀 없는 컴포넌트에서 불필요한 렌더링이 발생하게 됩니다.
만약 프로젝트가 커지면 더욱 비효율적이게 되겠죠? 물론 이런 문제를 해결하는 방법은 있지만(ex: 컨텍스트를 여러개 만들어 데이터를 잘게 쪼갠다.) 매우 불편합니다.
리덕스는 이러한 문제를 막아줍니다. a가 변경되면 a를 사용하는 컴포넌트만 리렌더링을 하게 최적화를 시켜줍니다. 이런 점이 관리를 더욱 편하게 해줍니다. 이렇게 전역 state값을 사용함에 있어서 Context보다 최적화가 잘 되어 있고, 상태관리도 해주는 등의 편의성 때문에 redux를 사용합니다.