[Redux] Redux를 사용하는 이유를 알아보자 (Context API + useReducer VS Redux)

강경서·2023년 11월 13일
1
post-thumbnail

Intro

Redux는 상태 관리를 위한 라이브러리입니다. 단방향으로 데이터가 흐르는 React에서 prop drilling을 방지하기 위해 많이들 사용하는 도구입니다. 상태 관리 라이브러리로는 Redux뿐만 아니라 다른 도구들도 존재하지만 오랜 시간동안 많은 개발자들이 사용하여 탄탄한 커뮤니티를 가진 건 Redux의 큰 장점입니다.


Redux

Redux 공식 문서에는 Redux를 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너라고 소개하고 있습니다. 기존의 MVC 패턴은 애플리케이션의 크기가 커짐에 따라 양방향으로 흐르는 데이터를 예측하지 못하는 한계를 가져 단방향으로 데이터를 전달하는 Flux 패턴으로 데이터를 예측 가능하게 만들었습니다. Redux는 이러한 Flux 패턴을 적용한 예측 가능한 상태 관리 라이브러리입니다.

MVC 패턴은 Model + View + Controller를 합친 용어로 ControllerModel의 데이터를 조회하거나 업데이트하는 역할을 하며, Model의 변화는 View에 반영됩니다. 그리고 사용자가 View를 통해 데이터를 입력하면, Model에 영향을 주면서 데이터를 관리하게 만드는 디자인 패턴입니다.

Flux 패턴은 데이터 흐름을 항상 Dispatcher에서 Store로, Store에서 View로, ViewAction을 통해 다시 Dispatcher로 데이터가 흐르는단방향 데이터 흐름을 가진 디자인 패턴입니다.


Redux 3가지 규칙

  1. 하나의 애플리케이션 안에는 하나의 Store
    하나의 애플리케이션에선 단 한개의 Store 를 만들어서 사용합니다. 여러 개의 Store 를 사용하는 것은 사실 가능하지만 권장하지 않습니다.

  2. State 는 읽기전용
    useState 처럼 Redux에서도 기존의 State 는 건들이지 않고 새로운 State를 생성하여 업데이트를 해주는 방식으로 합니다. Redux에서 불변성을 유지해야 하는 이유는 내부적으로 데이터가 변경 되는 것을 감지하기 위해서 입니다.

  3. 변화를 일으키는 함수, Redux 는 순수한 함수
    Redux 함수는 이전 StateAction 객체를 파라미터로 받습니다.
    이전의 State는 절대로 건들이지 않고, 변화를 일으킨 새로운 State 객체를 만들어서 반환합니다.


Redux 사용하기

React에서 Redux를 사용하기위해 react-redux, Redux를 효율적으로 사용하기 위해 @reduxjs/toolkitredux-logger 라이브러리 설치를 통해 실습을 했습니다. @reduxjs/toolkitRedux 로직을 작성하기 위해 공식적으로 추천하는 방법입니다


  • redux/todoSlice.js
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/toolkitcreateSlice함수를 사용하여 reduceraction을 만들 수 있습니다.


  • redux/store.js
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/toolkitconfigureStore함수를 사용하여 store를 생성할 수 있습니다. store에는 createSlice를 통해 만든 reducer를 넣어 사용할 수 있습니다.


  • index.js
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-reduxProveider에 만들어준 store를 넣어 컴포넌트를 감싸줍니다.


  • component/ReduxTodo.js
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;

Reduxstate를 관리하기 위해서는 react-reduxuseDispatchuseSelector를 사용합니다. useDispatch는 export한 action을 이용하여 state를 변경할 수 있습니다. seSelectorstorereducer를 선택하여 컴포넌트에state를 사용할 수 있습니다.


Redux를 사용하면 알 수 있듯이 ReactuseReducer와 같은 Flux 패턴을 활용한 방식으로 상태를 관리합니다. 그렇다면 useReducerContext API를 사용해서 상태를 관리할 수 있는데 라이브러리까지 설치하여 Redux를 사용하는 이유는 무었일까요?


Context API + useReducer VS Redux

분명히 React에서 자체적으로 제공하는 useReducerContext API를 사용해서 전역으로 상태를 관리할 수 있습니다. 하지만 Redux를 사용하여 상태관리를 하는 이유는 분명이 존재합니다.

Redux를 사용하는 이유

리렌더링으로 인한 성능 문제

Context API + useReducer의 경우 상태가 생성 및 변경할 때 해당 Context내부에 포함된 컴포넌트들중 일부만이 상태를 사용하더라도 전체 컴포넌트가 강제로 리렌더링 되기 때문에 성능 문제가 발생 할 수 있습니다. 물론 useMemo를 통해서 일정 부분 이슈를 해결할 수 있습니다. 다만 이를 위해 Context의 컴포넌트 모두 useMemo를 통해 생성해야하니 불필요한 과정이 포함될 수 있습니다.

유연한 Redux의 특징

Context API + useReducerReact의 기능이기 때문에 React 외부에서는 사용이 불가능합니다. 하지만 Redux는 UI 독립적이기 때문에 React 뿐만 아니라 Vue와도 잘 결합되며, vanilla JS와도 결합이 됩니다.

React Devtools vs Redux DevTools

React DevTools를 사용하면 현재의 상태 값은 볼 수 있지만 전달된 action, 과 payload, 처리 된 후의 상태등 시간에 따른 변화를 볼 수 없습닏다. Redux Devtools 을 이용하면 시간에 따른 상태 차이를 볼 수 있다.

그 외에도 Redux는 미들웨어를 이용하여 개발에 편의를 제공하기도 합니다. Context API + useReducer는 단순히 prop-drilling을 피하기 위해서 사용하기 좋아보이지만 복잡한 상태관리, 성능 이슈, 개발의 편의성등을 생각한다면 Redux는 꼭 필요한 상태 관리 도구입니다.


📝 후기

상태 관리 라이브러리로는 Recoil을 더 많이 사용해 왔습니다. 코드가 훨씬 간편하며 사용하는데 편했기에 굳이 Redux를 사용할 필요를 느끼지 못했습니다. 하지만 Redux를 통해 공부해보니 Flux 패턴의 필요성과 구조를 이해하는데에는 도움이 되었습니다. 뿐만 아니라 Redux가 가진 탄탄한 커뮤니티를 이용할수 있어 앞으로는 애플리케이션에 필요한 상태 관리 도구를 선택할 수 있는 선택지가 늘어났습니다.


🧾 Reference

profile
기록하고 배우고 시도하고

0개의 댓글