Development/ReactJs

[React] 리액트 Redux - 개요, 사용 및 활용법

재은초 2025. 3. 9. 23:35
반응형

Redux 개요

Flux 패턴

  • MVC 패턴이란 Model에 데이터를 저장하고, Controller를 사용하여 Model의 데이터를 관리하는 소프트웨어 디자인 패턴의 하나다. 이러한 MVC 패턴에서는 사용자가 View를 통해 데이터를 입력하면 View에서도 Model를 업데이트 할 수 있기 때문에 데이터가 양방향으로 전달될 수 있다.
  • 하지만 이렇게 구현된 프로젝트는 프로젝트의 규모가 커질수록 수많은 Model과 View가 생성되고, 이때 Model의 상태에 변화가 생길 경우 Model과 View 사이에 엄청난 양의 데이터가 양방향으로 전달되게 된다. 이로 인해 데이터의 흐름을 예측하기가 점점 힘들어지고, 수많은 버그를 발생시키는 원인이 된다.

https://www.tcpschool.com/react/react_redux_intro

  • 이러한 문제를 해결하기 위해 2014년 페이스북에서는 Flux 패턴이라는 새로운 아키텍처를 제안했다. Flux 패턴은 사용자 입력을 기반으로 Action을 생성하고, 이를 Dispatcher에 전달하여 Store의 데이터를 변경한 뒤 View에 반영하는 단방향의 데이터 흐름을 가지는 소프트웨어 아키텍처다. Flux 패턴으로 구현된 프로젝트는 데이터가 단방향으로만 전달되기 때문에 데이터의 흐름을 파악하기가 용이하고, 그 결과를 쉽게 예측할 수 있다는 장점을 가진다.
  • 이러한 Flux 패턴에 Dan Abramov이라는 개발자가 Reducer를 결합하여 만든 것이 바로 우리가 이번 장에서 살펴 볼 리덕스(Redux)다.

https://www.tcpschool.com/react/react_redux_intro

Redux란?

  • 우리는 앞서 컴포넌트 내에서 유동적으로 값을 관리할 수 있는 데이터인 state에 대해 알아보았으며, prop을 통해 React 컴포넌트 간에 데이터를 전달하는 방법도 살펴보았다.
  • 하지만 애플리케이션의 규모와 복잡도가 증가할수록 컴포넌트끼리 state를 공유해야 할 경우 수많은 컴포넌트를 복잡하게 거쳐야만 그 값을 전달할 수 있게 된다. 또한, 필요에 따라 state를 비동기적으로 변경해야 할 경우도 많아지게 되었다. 따라서 state를 보다 효과적으로 관리할 수 있는 방법의 중요성이 대두되었으며, 이렇게 등장한 여러 상태 관리 라이브러리 중 하나가 바로 Redux다.
  • Redux는 현재 많은 개발자들이 상태 관리를 위해 즐겨 사용하고 있는 자바스크립트 상태 관리 라이브러리다. Redux를 사용하면 state 관리 로직을 컴포넌트와 분리시킴으로써 효율적으로 관리할 수 있게 되며, 컴포넌트끼리 state를 공유할 때 여러 컴포넌트를 거쳐 prop를 복잡하게 전달(Props Drilling)하지 않아도 손쉽게 값을 전달할 수 있게 된다.

https://www.tcpschool.com/react/react_redux_intro

Redux의 동작 원리

  • Redux는 Store라는 전역 상태 저장소를 통해 state와 Reducer를 저장한다. Redux는 애플리케이션 하나 당 하나의 Store만을 생성하며, 유용한 몇 가지 내장 함수를 같이 제공한다. 각 컴포넌트들은 이러한 내장 함수들을 사용하여 Store의 데이터에 접근하고 변경을 요청할 수 있다.
  • Store를 변경하기 위한 로직을 저장하는 곳이 바로 Reducer다. Reducer는 Flux 패턴에는 없는 Redux만의 고유한 개념으로 현재 state와 Action을 인수로 전달 받아 Store에 접근하고, 전달 받은 Action을 참고하여 새로운 state를 만들어 반환한다.
  • 이러한 Reducer를 원하는 조건에 따라 호출하는 것이 바로 Action이다. state에 어떠한 변화가 필요할 때 Action을 발생시킴으로써(Dispatch) Reducer에 전달한다.
  • 마지막으로 Dispatch와 함께 Store의 내장 함수 중 하나인 subscribe를 통해 Action이 Dispatch 될 때마다 인수로 전달한 함수를 호출하도록 설정할 수 있다.

https://www.tcpschool.com/react/react_redux_intro

  • Redux에서 state를 관리하는 작업의 흐름은 다음과 같이 진행된다.
    • 사용자가 UI를 통해 컴포넌트 내에 존재하는 이벤트를 호출한다.
    • 이벤트와 연결된 액션 생성 함수(Action Creator)가 호출된다.
    • 액션 생성 함수에서 생성된 Action이 호출된다.
    • 호출된 Action이 Reducer로 전달(Dispatch)된다.
    • Reducer에서 Dispatch 된 Action에 따라 state 값을 변경한다.
    • 변경된 state가 렌더링되어 UI에 표시된다.

Redux의 세 가지 원칙

  • 이러한 Redux에서는 반드시 준수해야만 하는 세 가지 원칙이 있다.
    • 하나의 애플리케이션 안에는 하나의 Store만 사용해야 한다. 이렇게 함으로써 서버로부터 가져온 state가 직렬화(serialization)된 채 전달될 수 있으며, 클라이언트 측에서는 이를 추가적인 코드 없이도 곧바로 사용할 수 있으며 또한, 디버깅도 용이해 진다.
    • state는 읽기 전용이다. state를 변화시키는 유일한 방법은 Action을 전달하는 것 뿐이다. 이를 통해 모든 state 변화를 중앙에서 관리할 수 있으며, state 변경에 대한 추적이 용이해 진다.
    • state의 변화를 일으키는 Reducer는 순수 함수로 작성되어야 한다. Reducer는 현재 state와 Action을 전달 받아 다음 state를 반환하는 순수 함수다. 즉, 이전 state를 변경하는 것이 아니라 새로운 state 객체를 생성하여 반환하는 것임을 잊지 말아야 한다.

Redux의 필요성

  • Reudx를 사용하면 전역 상태 관리를 매우 효과적으로 수행할 수 있게 된다. 하지만 이러한 전역 상태 관리는 Redux 뿐만 아니라 Mobex, Recoil, Zotai 등 다른 상태 관리 라이브러리를 대신 사용할 수도 있으며, React의 Context API를 통해서도 충분히 동일한 작업을 수행할 수 있다.
  • 또한, Redux를 사용하게 되면 단 하나의 state를 바꾸기 위해 요청을 전달하는 Action과 상태를 바꿔주는 Redux를 생성해야 하는 등 추가적인 코드의 양이 늘어나게 된다. 따라서 상태 관리를 위해서는 반드시 Redux를 사용해야 한다는 것이 아니라 해당 프로젝트의 특징과 확장성 등을 충분히 고려하여 Redux의 도입 여부를 결정하는 것을 권장한다.

 

React에서 Redux 사용하기

Redux Toolkit과 React Redux 패키지 설치하기

  • 이번 장에서는 Redux의 공식 개발 도구인 Redux Toolkit을 사용하여 React에서 Redux를 설정하는 방법에 대해 살펴보도록 하겠다.
# npm인 경우
npm install @reduxjs/toolkit react-redux


# yarn인 경우
yarn add @reduxjs/toolkit react-redux

Redux Store 생성하기

  • React 프로젝트 src 폴더에 app 폴더를 생성하고 해당 폴더에 store.js 파일을 생성한다. 그리고 Redux Toolkit에서 configureStore 함수를 불러와 우선 빈 Store를 생성하고 내보낸다.
// store.js
import { configureStore } from "@reduxjs/toolkit";

const store = configureStore({
  reducer: {}
});

export default store;

Redux Store를 React에 제공하기

  • Store가 생성되면 src 폴더의 index.js 파일 내 존재하는 App 컴포넌트를 React Redux의 Provider 컴포넌트로 감쌈으로써 React 컴포넌트에서 Store를 사용할 수 있게 된다.
  • 방금 생성한 Redux Store를 불러와 App 컴포넌트를 감싼 Provider 컴포넌트에 prop으로 전달할 수 있도록 index.js의 코드를 다음과 같이 수정한다.
// index.js
import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import store from "./app/store";
import { Provider } from "react-redux";
import "./styles.css";

import App from "./App";

const root = createRoot(document.getElementById("root"));
root.render(
  <StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </StrictMode>
);

Redux State Slice 생성하기

  • Redux Toolkit에서는 Action과 Reducer를 한 번에 생성할 수 있는 createSlice() 함수를 제공한다.
  • createSlice는 초깃값과 Reducer, Action을 하나의 객체로 전달 받으며, 생성된 slice 객체에서 Reducer와 Action을 다음과 같이 가져올 수 있다.
const { reducer } = counterSlice
const { increment, decrement, incrementByAmount } = counterSlice.actions;
  • 이제 src 폴더에 counterSlice.js 파일을 생성하고, 해당 파일에서 createSlice를 불러온다.
  • Redux Toolkit으로 slice를 만들기 위해서는 slice를 식별할 수 있는 문자열과 state의 초깃값, state를 업데이트하는 방법을 정의한 하나 이상의 Reducer 함수가 필요하다.
  • slice가 생성되면 생성된 Redux 액션 생성자와 전체 slice에 대한 Reducer 함수를 내보낼 수 있다.
// counterSlice.js
import { createSlice } from "@reduxjs/toolkit";

export const counterSlice = createSlice({
  name: "counter",
  // 초깃값
  initialState: {
    value: 0
  },
  // 리듀서
  reducers: {
    // 액션
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    }
  }
});

export const { increment, decrement } = counterSlice.actions;

export default counterSlice.reducer;

Store에 Slice Reducer 추가하기

  • 이제 counterSlice의 Reducer 함수를 가져와 Store에 추가하기 위한 코드를 store 컴포넌트에 추가한다.
  • Reducer 매개 변수 내부에 필드를 정의함으로써, Slice Reducer 함수를 사용하여 해당 상태의 모든 업데이트를 처리하도록 Store에 전달할 수 있다.
// store.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "../counterSlice";

const store = configureStore({
  reducer: {
    counter: counterReducer
  }
});

export default store;

React 컴포넌트에서 Redux state와 Action 사용하기

  • 마지막으로 React Redux Hook을 사용하여 React 컴포넌트가 Redux Store와 상호 작용할 수 있도록 설정한다.
  • useSelector Hook을 사용하면 Store의 데이터를 읽을 수 있으며, useDispatch Hook을 사용하면 Action을 Dispatch할 수 있다.
// App.js
import { useSelector, useDispatch } from "react-redux";
import { decrement, increment } from "./counterSlice";

const App = () => {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <div>
        <button onClick={() => dispatch(decrement())}>1씩 감소</button>
        <span>{count}</span>
        <button onClick={() => dispatch(increment())}>1씩 증가</button>
      </div>
    </div>
  );
};

export default App;

 

Redux 활용하기

Counter에 기능 추가하기

증감하는 수량 설정하기

  • 앞선 Counter 예제에서는 증가와 감소 버튼을 누를 때마다 Count 값이 1씩 증가하거나 감소하였다. 이번에는 Redux를 활용하여 사용자가 증감하는 수량을 직접 설정할 수 있도록 구현해 보자.
  • 우선 counterSlice 컴포넌트에 증감하는 수량을 설정하기 위한 Reducer 함수인 incrementByQuantity()를 정의한다. 이때 사용되는 Action 객체의 payload 필드에는 Action의 타입에 따라 실행에 필요한 추가적인 state값을 저장할 수 있다.
// counterSlice.js
export const counterSlice = createSlice({
  // ...
  reducers: {
    // ...
    incrementByQuantity: (state, action) => {
      state.value += action.payload;
    }
  }
});

export const {
  increment,
  decrement,
  incrementByQuantity
} = counterSlice.actions;

export const selectCount = (state) => state.counter.value;

export default counterSlice.reducer;
// App.js
import { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import {
  decrement,
  increment,
  incrementByQuantity,
  selectCount
} from "./counterSlice";

const App = () => {
  const count = useSelector(selectCount);
  const dispatch = useDispatch();
  const [incrementQuantity, setIncrementQuantity] = useState("2");

  return (
    <div>
      <div>
        // ...
      </div>
      <div>
        <input
          value={incrementQuantity}
          onChange={(e) => setIncrementQuantity(e.target.value)}
        />
        <button
          onClick={() =>
            dispatch(incrementByQuantity(Number(incrementQuantity)))
          }
        >
          씩 증가
        </button>
      </div>
    </div>
  );
};

export default App;

비동기적으로 증감시키기

  • 이번에는 Count 값을 증가시키는 버튼을 눌렀을 때 1초 간의 딜레이 후 동작하도록 기능을 추가해 보자.
  • 우선 counterSlice 컴포넌트에 아래 코드를 추가한다. 아래 함수는 보통 thunk라고 불리며, 비동기 로직을 수행할 수 있도록 해 주는 코드다. 이 함수는 일반 Action처럼 Dispatch 될 수 있다. 이제 Dispatch 함수의 첫 번째 인수로 thunk가 호출되며, 이후 비동기 코드가 실행되고 다른 Action들이 Dispatch 될 것이다.
// counterSlice.js
export const incrementAsync = (quantity) => (dispatch) => {
  setTimeout(() => {
    dispatch(incrementByQuantity(quantity));
  }, 1000);
};
// App.js
import { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import {
  decrement,
  increment,
  incrementByQuantity,
  incrementAsync,
  selectCount
} from "./counterSlice";

const App = () => {
  // ...
  return (
    <div>
      <div>
        // ...
      </div>
      <div>
        // ...
        <button
          onClick={() =>
            dispatch(incrementAsync(Number(incrementQuantity) || 0))
          }
        >
          씩 증가(비동기)
        </button>
      </div>
    </div>
  );
};

export default App;

 

Reference

반응형