Redux
는 상태 관리를 위한 라이브러리입니다. 단방향으로 데이터가 흐르는 React
에서 prop drilling
을 방지하기 위해 많이들 사용하는 도구입니다. 상태 관리 라이브러리로는 Redux
뿐만 아니라 다른 도구들도 존재하지만 오랜 시간동안 많은 개발자들이 사용하여 탄탄한 커뮤니티를 가진 건 Redux
의 큰 장점입니다.
Redux
공식 문서에는 Redux
를 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너라고 소개하고 있습니다. 기존의 MVC
패턴은 애플리케이션의 크기가 커짐에 따라 양방향으로 흐르는 데이터를 예측하지 못하는 한계를 가져 단방향으로 데이터를 전달하는 Flux
패턴으로 데이터를 예측 가능하게 만들었습니다. Redux
는 이러한 Flux 패턴을 적용한 예측 가능한 상태 관리 라이브러리입니다.
MVC 패턴은 Model + View + Controller를 합친 용어로 Controller는 Model의 데이터를 조회하거나 업데이트하는 역할을 하며, Model의 변화는 View에 반영됩니다. 그리고 사용자가 View를 통해 데이터를 입력하면, Model에 영향을 주면서 데이터를 관리하게 만드는 디자인 패턴입니다.
Flux 패턴은 데이터 흐름을 항상 Dispatcher에서 Store로, Store에서 View로, View는 Action을 통해 다시 Dispatcher로 데이터가 흐르는단방향 데이터 흐름을 가진 디자인 패턴입니다.
하나의 애플리케이션 안에는 하나의 Store
하나의 애플리케이션에선 단 한개의 Store
를 만들어서 사용합니다. 여러 개의 Store
를 사용하는 것은 사실 가능하지만 권장하지 않습니다.
State 는 읽기전용
useState
처럼 Redux
에서도 기존의 State
는 건들이지 않고 새로운 State
를 생성하여 업데이트를 해주는 방식으로 합니다. Redux
에서 불변성을 유지해야 하는 이유는 내부적으로 데이터가 변경 되는 것을 감지하기 위해서 입니다.
변화를 일으키는 함수, Redux 는 순수한 함수
Redux
함수는 이전 State
와 Action
객체를 파라미터로 받습니다.
이전의 State
는 절대로 건들이지 않고, 변화를 일으킨 새로운 State
객체를 만들어서 반환합니다.
React
에서 Redux
를 사용하기위해 react-redux
, Redux
를 효율적으로 사용하기 위해 @reduxjs/toolkit
와 redux-logger
라이브러리 설치를 통해 실습을 했습니다. @reduxjs/toolkit
은 Redux
로직을 작성하기 위해 공식적으로 추천하는 방법입니다
import { createSlice } from "@reduxjs/toolkit";
const initialState = [];
const todoSlice = createSlice({
name: "Todo",
initialState,
reducers: {
addTodoStore(state, action) {
return [...state, { ...action.payload }];
},
removeTodoStore(state, action) {
return state.filter((todo) => todo.id !== action.payload);
},
clearTodoStore(state, action) {
return state.forEach((todo) => {
if (todo.id === action.payload) {
todo.clear = !todo.clear;
}
});
},
},
});
export const { addTodoStore, removeTodoStore, clearTodoStore } =
todoSlice.actions;
export default todoSlice.reducer;
@reduxjs/toolkit
의 createSlice
함수를 사용하여 reducer
와 action
을 만들 수 있습니다.
import { configureStore } from "@reduxjs/toolkit";
import todoSlice from "./todoSlice";
import logger from "redux-logger";
const store = configureStore({
reducer: {
TodoStore: todoSlice,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
});
export default store;
@reduxjs/toolkit
의 configureStore
함수를 사용하여 store
를 생성할 수 있습니다. store
에는 createSlice
를 통해 만든 reducer
를 넣어 사용할 수 있습니다.
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { Provider } from "react-redux";
import store from "./redux/store";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
Redux
를 전역으로 사용하기 위해 react-redux
의 Proveider
에 만들어준 store
를 넣어 컴포넌트를 감싸줍니다.
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
addTodoStore,
clearTodoStore,
removeTodoStore,
} from "../redux/todoSlice";
const ReduxTodo = () => {
const dispatch = useDispatch();
const todos = useSelector((state) => state.TodoStore);
const [value, setValue] = useState("");
const addTodo = (event) => {
event.preventDefault();
dispatch(addTodoStore({ todo: value, id: Date.now(), clear: false }));
setValue("");
};
return (
<>
<h1>할 일 목록</h1>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<span style={{ textDecoration: todo.clear && "line-through " }}>
{todo.todo}
</span>
<button onClick={() => dispatch(clearTodoStore(todo.id))}>✔</button>
<button onClick={() => dispatch(removeTodoStore(todo.id))}>
❌
</button>
</li>
))}
</ul>
<form onSubmit={addTodo}>
<input
type="text"
value={value}
onChange={(event) => setValue(event.target.value)}
/>
<input type="submit" value={"추가하기"} />
</form>
</>
);
};
export default ReduxTodo;
Redux
의 state
를 관리하기 위해서는 react-redux
의 useDispatch
와 useSelector
를 사용합니다. useDispatch
는 export한 action
을 이용하여 state
를 변경할 수 있습니다. seSelector
는 store
의 reducer
를 선택하여 컴포넌트에state
를 사용할 수 있습니다.
Redux
를 사용하면 알 수 있듯이 React
의 useReducer
와 같은 Flux
패턴을 활용한 방식으로 상태를 관리합니다. 그렇다면 useReducer
와 Context API
를 사용해서 상태를 관리할 수 있는데 라이브러리까지 설치하여 Redux
를 사용하는 이유는 무었일까요?
분명히 React
에서 자체적으로 제공하는 useReducer
와 Context API
를 사용해서 전역으로 상태를 관리할 수 있습니다. 하지만 Redux
를 사용하여 상태관리를 하는 이유는 분명이 존재합니다.
Context API + useReducer
의 경우 상태가 생성 및 변경할 때 해당 Context
내부에 포함된 컴포넌트들중 일부만이 상태를 사용하더라도 전체 컴포넌트가 강제로 리렌더링 되기 때문에 성능 문제가 발생 할 수 있습니다. 물론 useMemo
를 통해서 일정 부분 이슈를 해결할 수 있습니다. 다만 이를 위해 Context
의 컴포넌트 모두 useMemo
를 통해 생성해야하니 불필요한 과정이 포함될 수 있습니다.
Context API + useReducer
는 React
의 기능이기 때문에 React
외부에서는 사용이 불가능합니다. 하지만 Redux
는 UI 독립적이기 때문에 React
뿐만 아니라 Vue
와도 잘 결합되며, vanilla JS
와도 결합이 됩니다.
React DevTools
를 사용하면 현재의 상태 값은 볼 수 있지만 전달된 action, 과 payload, 처리 된 후의 상태등 시간에 따른 변화를 볼 수 없습닏다. Redux Devtools
을 이용하면 시간에 따른 상태 차이를 볼 수 있다.
그 외에도 Redux는 미들웨어를 이용하여 개발에 편의를 제공하기도 합니다. Context API + useReducer
는 단순히 prop-drilling
을 피하기 위해서 사용하기 좋아보이지만 복잡한 상태관리, 성능 이슈, 개발의 편의성등을 생각한다면 Redux
는 꼭 필요한 상태 관리 도구입니다.
상태 관리 라이브러리로는 Recoil
을 더 많이 사용해 왔습니다. 코드가 훨씬 간편하며 사용하는데 편했기에 굳이 Redux
를 사용할 필요를 느끼지 못했습니다. 하지만 Redux
를 통해 공부해보니 Flux
패턴의 필요성과 구조를 이해하는데에는 도움이 되었습니다. 뿐만 아니라 Redux
가 가진 탄탄한 커뮤니티를 이용할수 있어 앞으로는 애플리케이션에 필요한 상태 관리 도구를 선택할 수 있는 선택지가 늘어났습니다.