⚛️ React 공부/기초 문법

ReactJS 숙련: Redux 리덕스, counter 기능 구현하기

seheej 2024. 8. 21. 20:52

Redux 소개

Redux란?전역상태 라이브러리

 

1. 리덕스가 필요한 이유

  1. useState의 불편함
    1. props를 통해 부모 컴퍼넌트->자식 컴퍼넌트로 값을 보내줘야 했음 (prop drilling 유발)
    2. 자식 컴포넌트에서 부모 컴포넌트로 값을 보낼 수 없음
    3. useContext를 사용하는 것과 유사하게 전체영역에서 특정 경로(위치)에 있는 store라는 개념에서부터 데이터를 공유받음
    4. 그렇기 때문에, 리덕스를 사용하면 전역상태를 사용할 수 있어 
  2. Global State(Redux)와 Local State(useState)
    1. Local State: useState를 이용해 생성한 state
    2. Global State: 컴포넌트에서 생성되지 않음, 중앙화 된 곳에서 state들이 생성됨 => 컴포넌트가 어디에 위치하고 있든 상관없이 state를 불러와 사용할 수 있음

2. 리덕스?

: “중앙 state 관리소”를 사용할 수 있게 도와주는 패키지(라이브러리)

 

3. 리덕스의 흐름

 

  1. View에서 사용자 action 발생 (ex. click)
  2. Dispatch를 통해 action 전달
  3. Reducer가 액션을 처리하기 전 Middleware가 실행됨
  4. Reducer가 실행
  5. Store에 새로운 값을 저장
  6. Store의 상태 변경을 감지한 UI가 업데이트

 

4. 주요 개념 복습

  1. 액션 객체: 반드시 type이라는 키를 포함하며, 리듀서로 보낼 명령을 나타냄
  2. 디스패치(dispatch): 액션 객체를 리듀서로 전달하는 함수입니다. useDispatch() 훅을 사용해 호출
  3. 리듀서: 전달받은 액션 객체에 따라 조건이 일치했을 때 새로운 상태를 생성하는 함수
  4. 액션 객체의 type은 대문자로 작성: JavaScript에서 상수는 대문자로 작성

 

5. 리덕스 설치 및 설정

  • 패키지 설치: Redux와 React-Redux를 설치합니다.
  • yarn add redux react-redux
  • 폴더 구조 생성: src/redux/config와 src/redux/modules 폴더를 생성합니다.
    • configStore.js: 중앙 상태 관리소인 Store를 설정하는 파일입니다.
    • modules 폴더에는 상태 관리 로직을 작성한 모듈 파일들이 위치합니다.
  • Store 설정 (src/redux/config/configStore.js):
  • import { createStore } from "redux";
    import { combineReducers } from "redux";
    import counter from "../modules/counter";
    
    // 1. rootReducer 만듬
    const rootReducer = combineReducers({
    counter: counter,  // 뒤의 counter를 생략하여 counter만 작성해도 됨
    });	// 모듈 안에 들어갈 값들이 combineReducers의 인풋(객체)으로 들어감
    
    // 2. store를 조합
    const store = createStore(rootReducer);
    
    // 3. 만든 store 내보내기
    export default store;
  • Provider 설정 (src/main.jsx):
  • import React from "react";
    import ReactDOM from "react-dom/client";
    import App from "./App.jsx";
    import "./index.css";
    import { Provider } from "react-redux";
    import store from "./redux/config/configStore.js";
    
    ReactDOM.createRoot(document.getElementById("root")).render(
      <React.StrictMode>
        <Provider store={store}>
          <App />
        </Provider>
      </React.StrictMode>
    );

 

6. 첫 번째 모듈 작성 (Counter)

  1. 모듈 생성 (src/redux/modules/counter.js)
  2. Store와 모듈 연결: 이미 configStore.js에서 설정을 완료
// 초기값 설정: 객체를 가지고 key로 number =0을 가짐
const initialState = {
  number: 0,
};

const PLUS_ONE = "PLUS_ONE";

export const plusOne = () => { 
  return {
    type: PLUS_ONE
  }
}
// reduce 함수 생성: 인자를 2개 받음 (리덕스 사용방법)
const counter = (state = initialState, action) => {
  // action은 type을 가진 객체
  switch (action.type) {
    case "PLUS_ONE": // action.type이 PLUS ONE인 경우
      return {
        number: state.number + 1, // 기존의 초기값 number: 0이 아니라, 새로운 객체 number: 1을 반환
      };
    case "MINUS_ONE":
      return {
        number: state.number - 1,
      };
    default:
      return state; // default로 state를 그대로 반환
  }
};

export default counter;

 

7. 스토어와 모듈 연결 확인

  1. 스토어 조회: useSelector 훅을 사용하여 스토어에서 상태를 조회할 수 있음
import React from "react";
import { useDispatch, useSelector } from "react-redux";

const App = () => {
  const conterReducer = useSelector((state) => {
    return state.counter;
  });
  console.log("state", conterReducer);
  const dispatch = useDispatch(); // dispatch가 액션 객체를 redux에 보내는 역할
  return (
    <div>
      {conterReducer.number}
      <br />
      <button
        // 마우스를 클릭했을 때 dispatch를 실행
        onClick={() => {
          dispatch(
            // dispatch를 통해 아래 부분이 counter.js의 counter의 인자 action으로 가게 됨
            {
              // 스토어에 변경이 생기려면 action 객체를 보내야 함
              // 객체에는 key-value pair로 type과 type의 이름이 들어가야 함

              type: "PLUS_ONE",
            }
          );
        }}
      >
        +1
      </button>
      <button
        onClick={() => {
          dispatch({
            type: "MINUS_ONE",
          });
        }}
      >
        -1
      </button>
    </div>
  );
};

export default App;

 

8. Redux로 Counter 기능 구현하기: +1, -1 기능

 

(1) +1 기능 구현하기

1.액션 객체 생성

: 액션 객체는 반드시 type 키를 포함해야 합니다. 예를 들어, 숫자에 1을 더하는 액션 객체는 아래와 같이 정의됩니다:

{ type: "PLUS_ONE" }

 

2. 디스패치로 액션 보내기

디스패치는 리덕스의 useDispatch 훅을 사용해 액션 객체를 리듀서로 보냅니다. App.js에서 버튼 클릭 시 디스패치를 호출합니다:

import React from "react";
import { useDispatch } from "react-redux";

const App = () => {
  const dispatch = useDispatch();

  return (
    <div>
      <button
        onClick={() => {
          dispatch({ type: "PLUS_ONE" });
        }}
      >
        + 1
      </button>
    </div>
  );
};

export default App;

 

3. 리듀서 로직 추가

리듀서는 액션의 type에 따라 상태를 변경합니다. counter.js에서 PLUS_ONE에 대한 로직을 추가합니다:

// 초기 상태값
const initialState = {
  number: 0,
};

// 리듀서
const counter = (state = initialState, action) => {
  switch (action.type) {
    case "PLUS_ONE":
      return {
        number: state.number + 1,
      };
    default:
      return state;
  }
};

export default counter;

 

(2) -1 기능 구현하기

1. 빼기 버튼 추가

App.js에 -1 버튼을 추가합니다:

<button
  onClick={() => {
    dispatch({ type: "MINUS_ONE" });
  }}
>
  - 1
</button>

 

2. 리듀서 로직 확장

리듀서에 MINUS_ONE에 대한 로직을 추가합니다:

const counter = (state = initialState, action) => {
  switch (action.type) {
    case "PLUS_ONE":
      return {
        number: state.number + 1,
      };
    case "MINUS_ONE":
      return {
        number: state.number - 1,
      };
    default:
      return state;
  }
};

 

(3) 변경된 상태 확인하기

리덕스의 상태를 확인하기 위해 useSelector를 사용하여 상태를 가져옵니다. 이 값을 화면에 출력합니다:

import { useSelector } from "react-redux";

const number = useSelector((state) => state.counter.number);
return (
  <div>
    {number}
    <button onClick={() => dispatch({ type: "PLUS_ONE" })}>+ 1</button>
    <button onClick={() => dispatch({ type: "MINUS_ONE" })}>- 1</button>
  </div>
);

 

(4) 마무리

이제 +1, -1 버튼을 클릭할 때마다 화면에 표시된 숫자가 증가하고 감소하는 것을 확인할 수 있습니다. 리덕스를 통해 상태를 관리하면서, 각 컴포넌트가 리덕스 상태를 반영하여 리렌더링되는 과정을 이해할 수 있었습니다.