[React] Redux 구조와 Toolkit 활용하기(feat.Flux pattern)
1. Redux란?
리덕스는 전역 상태관리 라이브러리이다. 리액트는 부모 컴포넌트에서 자식컴포넌트로 데이터를 내려주게되는데 자식 컴포넌트끼리 데이터를 주고 받으려면 부모를 통해 받아와야하기 때문에 매우 복잡해진다. 자식컴포넌트가 많아지면 상위컴포넌트에서 계속 데이터를 내려받게되고 props drilling 이슈가 발생한다.
이 현상을 방지하기 위해 Redux를 사용하는데 중앙 State 관리소에서 State를 생성하고 만약 어떤 컴포넌트에서 State가 필효하다면 컴포넌트가 어디에 위치하고 있던 상관없이 State를 불러와서 사용할 수 있게 된다. 이렇게 특정 컴포넌트에 종속되어 있는 것이 아니라 "중앙 State 관리소"에서 생성된 state를 Global State라고 한다. 그리고 이러한 값들을 관리하는 것을 전역 상태관리라고 한다. useState로 생성한 State는 Local State이고, 리덕스에서 생성한 State는 Global State 라고 한다.
2. Flux Pattern이란?
이전의 데이터 처리과정은 MVC(Model-view-controller) 패턴으로 이루어졌는데 대규모 프로젝트에서는 MVC가 너무 복잡해짐에 따라 예측할 수 없는 오류가 발생하곤 했다.


이러한 수많은 변경들은 비동기적으로 생길 수도 있고, 하나의 변경이 다수의 변경들을 일으킬 수 있어 데이터 흐름을 디버그하기 어렵게 만든다.
한편 Flux는 단방향 데이터 흐름으로 변화를 예측하기가 훨씬 쉽다. 새로운 데이터를 넣으면 처음부터 흐름이 다시 시작된다.

3. Redux의 요소
redux는 flux패턴을 구현한 라이브러리로 flux 패턴의 구성요소를 따라간다. 하지만 flux에는 없는 reducer라는 요소가 있다.

Action (액션)
액션 생성자는 애플리케이션의 상태나 뷰를 업데이트 하기위한 액션을 생성한다. 액션 생성자는 type과 payload를 포함하고 있으며 type은 어떤 액션인지 표시해주는 이름표같은 역할을 하고 payload는 전달해주고 싶은 값을 의미한다고 생각하면 좋다.
디스패쳐(dispatcer)
디스패쳐는 액션 생성자가 보낸 주문을 type에 따라 리듀서로 전달해주는 역할을 한다.
Store (스토어)
스토어는 애플리케이션 내의 모든 상태와 그와 관련된 로직을 가지고 있다.
Reducer (리듀서)
액션이 전달되면 type에 맞는 로직을 실행하고 상태를 업데이트한다.
4. Redux Toolkit이란?
리덕스 전체 코드의 양을 줄이기 위해 생긴 API입니다. Ducks 패턴의 요소들을 좀 더 간결하게 작성하기 위해 사용된다.
- 리덕스 툴킷 설치
yarn add react-redux @reduxjs/toolkit
- action value, action creator, reducer
리덕스 : action value와 action creaotr 따로 선언해준뒤 리듀서에서 로직을 실행합니다.
// Action Value
const ADD_NUMBER = "ADD_NUMBER";
// Action Creator
export const addNumber = (payload) => {
return {
type: ADD_NUMBER,
payload,
};
};
// Initial State
const initialState = {
number: 0,
};
// Reducer
const counter = (state = initialState, action) => {
switch (action.type) {
case ADD_NUMBER:
return {
number: state.number + action.payload,
};
default:
return state;
}
};
// export default reducer
export default counter;
리덕스 툴킷 : action value와 action creator를 따로 생성하지 않고 createSlice라는 api를 사용해 리듀서에서 한번에 생성됩니다.
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
number: 0,
};
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
// action value는 함수의 이름으로 하고 action creator까지 한번에 작성할 수 있다.
addNumber: (state, action) => {
state.number = state.number + action.payload;
},
},
});
// 액션크리에이터는 컴포넌트에서 사용하기 위해 export 하고
export const { addNumber } = counterSlice.actions;
// reducer 는 configStore에 등록하기 위해 export default 합니다.
export default counterSlice.reducer;
- configStore
리덕스 : createStore와 combineStore를 사영해서 store를 생성하고 reducer를 연결해준다.
import { createStore } from "redux";
import { combineReducers } from "redux";
import counter from "../modules/counter";
import counter from "../modules/todos";
const rootReducer = combineReducers({
counter, todos
});
const store = createStore(rootReducer);
export default store;
리덕스 툴킷 : configureStore api하나만 작성해주면 store에 reducer를 연결할 수 있다.
import { configureStore } from "@reduxjs/toolkit";
// import 해온 것은 slice.reducer 입니다.
import counter from "../modules/counterSlice";
import todos from "../modules/todosSlice";
// 모듈(Slice)이 여러개인 경우
// 추가할때마다 reducer 안에 각 모듈의 slice.reducer를 추가해줘야 합니다.
// 아래 예시는 하나의 프로젝트 안에서 counter 기능과 todos 기능이 모두 있고,
// 이것을 각각 모듈로 구현한 다음에 아래 코드로 2개의 모듈을 스토어에 연결해준 것 입니다.
const store = configureStore({
reducer: { counter: counter, todos: todos },
});
export default store;
- App.js
import React from "react";
import { useSelector } from "react-redux";
const App = () => {
// Store에 있는 todos 모듈 state 조회하기
const todos = useSelector((state) => state.todos);
// Store에 있는 counter 모듈 state 조회하기
const counter = useSelector((state) => state.counter);
return <div>App</div>;
};
export default App;
- Redus Toolkit 구조
Redux 폴던 안에 config와 modules 폴더를 만들어 각각 configureStore, counter, todos 파일을 넣어준다.
