React 상태관리 : Redux

코딩로그·2025년 2월 26일
post-thumbnail

Redux란?

Redux는 가장 대표적인 전역 상태 관리 라이브러리로, 상태(state)를 중앙에서 관리

✅ Redux의 핵심 개념

  • 전역 상태 관리: 여러 컴포넌트에서 공유할 상태를 Store에 보관
  • 단방향 데이터 흐름(Flux 패턴): 데이터가 한 방향으로만 흐름
  • Predictable(예측 가능): 동일한 액션(입력)에 대해 항상 동일한 결과(출력)를 보장

✅ Redux의 데이터 흐름 (FLUX 패턴)

  • UI : 사용자가 버튼 클릭 등 상태변화가 필요한 이벤트 발생
  • Action : 상태 변경을 위한 정보를 담은 객체 생성
  • Dispatch : Action을 Reducer로 전달하는 함수
  • Reducer : Action을 해석하여 그 값에 따라 Store의 전역 상태 변경
  • Store : 전역 상태가 저장되는 저장소, Reducer에 의해 변경된 상태는 UI에서 리렌더링

Redux의 구성 요소

1️⃣ Action

상태를 어떻게 변경할지 정의하는 객체

const increment = { type: "INCREMENT", payload: 5 };

또는 함수를 사용하여 동적으로 액션을 생성할 수도 있음

const increment = (num) => ({ type: "INCREMENT", payload: num });

2️⃣ Dispatch

Action을 Reducer로 넘겨주는 함수

dispatch({ type: "INCREMENT", payload: 5 });
dispatch(increment(5)); // 액션 생성자 함수 사용

3️⃣ Reducer

Action을 받아 상태를 변경하는 함수

const counterReducer = (state, action) => {
	switch(action.type) { 	// 어떤 액션 타입인지 검사
		case 'INCREMENT'    // 액션 타입의 값에 따라 다른 값을 리턴
			return state + action.payload   // 새로운 state값을 전달
				... ...
	}
}

4️⃣ Store

Redux의 전역 상태 저장소 , createStore 함수에 Reducer를 전달해서 생성

const store = createStore(counterReducer);

5️⃣ combineReducers

Reducer가 여러 개일 경우 combineReducers로 묶어서 사용

const rootReducer = combineReducers({ counter: counterReducer });
const store = createStore(rootReducer);

✅ 구성 요소를 다 만들었다면?

React-Redux에서 제공하는 기능을 사용해서

  • App과 전역 상태 저장소 연결하기 → <Provider store = {store}>
  • 상태 저장소에서 상태 꺼내서 사용하기 → useSelector()
  • dispatch 함수 만들어서 사용하기 → useDispatch()

⚙️ Redux-Thunk

  • Redux에서 비동기 처리를 할 수 있게 해주는 미들웨어
  • 액션으로 객체 말고도 함수를 사용할 수 있게 해줌으로써 가능
  • 작동 원리
    • 여러개의 코드를 묶어줄 수 있음
    • 여러 명령어 처리가 가능하기 때문에 비동기 처리가 가능

Redux 기본 사용법

1️⃣ Redux 설치

터미널에서 redux와 react-redux 설치

npm install redux react-redux

2️⃣ UI 생성

📁 App.jsx

import "./App.css";

function App() {
  return (
    <>
      <div>counter : 0</div>
      <button>+</button>
      <button>-</button>
    </>
  );
}

export default App;

3️⃣ Action 생성

📁 redux.js

const increment = { type: "increment" };
const decrement = { type: "decrement" };

4️⃣ Reducer 생성

📁redux.js

const counterReducer = (state = 0, action) => {
  switch (action.type) {
    case "increment":
      return state + 1;
    case "decrement":
      return state - 1;
    default:
      return state;
  }
};

5️⃣ Store 생성

📁 redux.js

const store = createStore(counterReducer);

6️⃣ Provider를 이용해 App과 Store 연결

📁 main.jsx

import { Provider } from "react-redux";
import { store } from "./redux";

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

7️⃣ useSelector를 이용하여 상태 가져오기 (App.jsx)

import { useSelector } from "react-redux";

function App() {
  const counter = useSelector((state) => state);
  return (
    <>
      <div>counter : {counter}</div>
      <button>+</button>
      <button>-</button>
    </>
  );
}

8️⃣ useDispatch를 이용해 상태 변경하기 (App.jsx)

import { useDispatch, useSelector } from "react-redux";

function App() {
  const counter = useSelector((state) => state);
  const dispatch = useDispatch();

  return (
    <>
      <div>counter : {counter}</div>
      <button onClick={() => dispatch(increment)}>+</button>
      <button onClick={() => dispatch(decrement)}>-</button>
    </>
  );
}

➕ 두 개 이상의 상태를 만들고 관리하고 싶을 때

1️⃣ Action : counterReducer1, counterReducer2 생성

const increment1 = {
  type: 'increment1'
}

const decrement1 = {
  type: 'decrement1'
}

const counterReducer1 = (state = 0, action) => { // counter의 초기값은 0
  switch (action.type) {
    case 'increment1' :
      return state + 1  // 새로운 상태 반환
    case 'decrement1' :
      return state - 1
    default :
      return state      // 예외처리
  }
}

const increment2 = {
  type: 'increment2'
}

const decrement2 = {
  type: 'decrement2'
}

const counterReducer2 = (state = 0, action) => { // counter의 초기값은 0
  switch (action.type) {
    case 'increment2' :
      return state + 2  // 새로운 상태 반환
    case 'decrement2' :
      return state - 2
    default :
      return state      // 예외처리
  }
}

2️⃣ Store : combineReducers함수를 이용해 두 개의 객체를 객체 인자로 받기

const rootReducer = combineReducers({counterReducer1, counterReducer2})

export const store = createStore(rootReducer)

3️⃣ useSelector : 꺼내올 상태를 각각 지정해서 가져오기

  const counter1 = useSelector((state) => state.counterReducer1);
  const counter2 = useSelector((state) => state.counterReducer2);

4️⃣ dispatch : 사용하기

function App() {
  const counter1 = useSelector((state) => state.counterReducer1);
  const counter2 = useSelector((state) => state.counterReducer2);
  const dispatch = useDispatch();

  return (
    <>
      <div>
        <h3>counter 1</h3>
        <div>counter : {counter1}</div>
        <button onClick={() => dispatch(increment1)}>+</button>
        <button onClick={() => dispatch(decrement1)}>-</button>
      </div>
      <div>
        <h3>counter2</h3>
        <div>counter : {counter2}</div>
        <button onClick={() => dispatch(increment2)}>+</button>
        <button onClick={() => dispatch(decrement2)}>-</button>
      </div>
    </>
  );
}

📌 4. Redux Thunk (비동기 처리)

Redux에서 비동기 작업(API 호출, setTimeout 등)을 처리할 수 있도록 해주는 미들웨어

1️⃣ Redux Thunk 설치

npm install redux-thunk

2️⃣ applyMiddleware(thunk)를 Store에 추가

  • store
    - 첫 번째 전달받은 인자가 상태를 관리할 reducer
    - 그 뒤에 추가적으로 다른 인자 전달 가능
    - applyMiddleware를 이용해 연결하고 싶은 미들웨어 연결
    📁 App.jsx
import "./App.css";
import { applyMiddleware, combineReducers, createStore } from "redux";
import { useDispatch, useSelector } from "react-redux";
import { thunk } from 'redux-thunk';
export const store = createStore(rootReducer, applyMiddleware(thunk));

3️⃣ dispatch를 이용하여 비동기 Thunk 액션 생성

      <div>
        <h3>함수로 전달하기 : Thunk</h3>
        <div>counter : {counter1}</div>
        <button onClick={() => dispatch((dispatch) => {
          setTimeout(() => {
            dispatch(increment1)
          }, 1000)
        })}>+</button>
        <button onClick={() => dispatch((dispatch) => {
          setTimeout(() => {
            dispatch(decrement1)
          }, 1000)
        })}>-</button>
      </div>
  1. onClick 이벤트가 발생하면 dispatch가 실행
  2. Thunk 방식 : dispatch 내부에 또 다른 dispatch가 들어가 있음 ((dispatch) => {...})
    • dispatch에 함수를 전달하면, Redux에서 이를 실행할 때 해당 함수를 호출
  3. setTimeout(() => { dispatch(increment1); }, 1000);
    • 1초 후에 increment1 액션을 디스패치

profile
hello world!

0개의 댓글