본문 바로가기
React

[React] Redux (+Redux Toolkit)

by 파프리카_ 2023. 7. 23.
728x90
반응형

03_Redux

3-1. What is Redux?

  • 크로스 컴포넌트 또는 앱 와이드 상태를 위한 상태 관리 시스템

상태의 종류

  • Local State

    • 데이터가 변경되어 하나의 컴포넌트에 속하는 UI에 영향을 미치는 상태

    • ex) 토글버튼, 입력필드 ..

    • useState() , useReducer() 로 관리

  • Cross-Component State -> Redux 로 관리

    • 다수의 컴포넌트에 영향을 주는 상태

    • ex) 모달 컴포넌트

    • prop chains, prop drilling 으로 관리

  • App-Wide State -> Redux 로 관리

    • 애플리케이션의 모든 컴포넌트에 영향을 주는 상태

    • ex) 사용자 인증 상태

    • prop chains, prop drilling 으로 관리

3-2. Why use Redux?

  • 기존 React Context의 단점을 보완하기 위해

  • React Context의 단점?

    • 설정과 관리의 복잡성

      • 프로젝트의 규모가 커질 수록 심하게 중첩된 JSX문을 사용해야 할 수 있음
    • 성능

      • 고빈도 상태변경에 대해 최적화 되어있지 않음

3-3.Redux Basic

Redux 핵심기능

  • Centeral Data(State) Store (중앙 저장소) 가 존재하여, 어플리케이션 전체에서 해당 중앙 저장소에 있는 데이터를 공유해서 사용할 수 있다.

  • 리덕스에서는 한 애플리케이션당 하나의 스토어를 만든다.

Flow

  1. Central Data StoreComponents는 Subscription 하여 변경점이 있을 경우 알림을 준다.

  2. (데이터 상태 변경을 원할 시) Actiondispatch하여 trigger하고, 이때 변경 명령을 준다

  3. 해당 명령에 따라 ActionReducer Function에 이를 전달하고, 여기서 변경을 처리한다

  4. 변경된 데이터 상태는 다시 Central Data Store에 반영된다.

  5. Central Data Store에 반영된 변경된 데이터는 상태가 변경된 것을 구독한 Component 에 알림을 주고, 변경사항을 적용받고 그것이 UI에 적용된다.

시작하기

  1. 터미널에 아래 명령문 작성 -> package.json 파일 생성

    npm init -y
  2. 터미널에 아래 명령문을 작성하여, redux 설치

    npm install redux
  3. 기본 구조 생성

    • 아래의 코드는 counter 객체가 1씩 plus되는 기능을 간단히 구현

      // 0. node.js 문법으로 redux 라이브러리 호출
      const redux = require("redux");
      
      // 2. define reducer function
      // -> input : (기존 상태={객체의 초기값 설정}, 디스패치된 액션)
      // -> output : 새로운 상태의 객체
      const counterReducer = (state = { counter: 0 }, action) => {
       // action.type에 따른 동작 분기처리
       switch (action.type) {
         case 'increment':
           return {
             counter: state.counter + 1
           };
         case 'decrement':
           return {
             counter: state.counter - 1
           };
         default:
           return state;
       }
      };
      
      // 1. create central data store
      const store = redux.createStore(counterReducer);
      
      // 3. create subscriber
      // -> 저장소의 상태가 업데이트 될 때마다 트리거된다
      // -> 트리거되면 `getState()` 로 인해 최신 상태를 전송받음
      const counterSubscriber = () => {
       const latestState = store.getState();
       console.log(latestState);
      };
      // subscriber 함수를 실행하라는 명령
      store.subscribe(counterSubscriber);
      
      // action을 dispatch(발송)하는 메서드
      // action : javascript object (action type 설정)
      store.dispatch({type:'increment'}); // { counter: 1 }
      store.dispatch({type:'increment'}); // { counter: 2 }
      store.dispatch({type:'increment'}); // { counter: 3 }
      store.dispatch({type:'decrement'}); // { counter: 2 }
      store.dispatch({type:''}); // { counter: 2 }

3-4.Using Redux with React

  1. package 설치

    npm install redux eate-redux
  2. store 패키지에 index.js 작성

    import { createStore } from "redux";
    
    // reducer function
    const counterReducer = (state = { counter: 0 }, action) => {
      switch (action.type) {
        case "increment":
          return {
            counter: state.counter + 1,
          };
        case "decrement":
          return {
            counter: state.counter - 1,
          };
        default:
          return state;
      }
    };
    
    // create store
    const store = createStore(counterReducer);
    
    export default store;
    • dispatch나 action은 쓰지 않음 => 개별 컴퍼넌트에서 선언
  3. 최상위 단에 있는 index.js에 store를 제공받도록 설정

    import React from "react";
    import ReactDOM from "react-dom/client";
    import { Provider } from "react-redux";
    
    import "./index.css";
    import App from "./App";
    import store from "./store/index"; // store가 정의되어 있는 위치
    
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(
      // 어플리케이션 전역에서 store 공유 가능하도록 아래와 같이 감싸주기
      <Provider store={store}> 
        <App />
      </Provider>
    );
  4. 내부 컴포넌트에서 useSelector 를 이용해 데이터에 접근가능하도록 설정

    • actiondispatch 하기
    import { useSelector, useDispatch } from "react-redux";
    import classes from "./Counter.module.css";
    
    const Counter = () => {
      // useDispatch
      // -> redux store에 대한 action을 전송
      const dispatch = useDispatch();
    
      const incrementHandler = () => {
        dispatch({ type : 'increment' }); // 새로운 action을 보내기 위해 실행
      };
    
      const decrementHandler = () => {
        dispatch({ type : 'decrement' }); // 새로운 action을 보내기 위해 실행
      };
      // useSelector 
      //  -> 저장소가 관리하는 데이터에 접근
      //  -> 함수는 react-redux 가 실행 
      //     (useSelect를 정의한 컴포넌트에 자동으로 subscribe를 설정함)
      // userSelector(관리된 데이터 => 접근하고자 하는 상태의 부분)
      const counter = useSelector(state => state.counter); 
    
      const toggleCounterHandler = () => {};
    
      return (
        <main className={classes.counter}>
          <h1>Redux Counter</h1>
          <div className={classes.value}>{counter}</div>
          <div>
            <button onClick={incrementHandler}>Increment</button>
            <button onClick={decrementHandler}>Decrement</button>
          </div>
          <button onClick={toggleCounterHandler}>Toggle Counter</button>
        </main>
      );
    
    };
    
    export default Counter;
  5. UI 실행 화면

    5-1. Increment 버튼 클릭

    5-2. {counter} 값 +1

5-3. `Decrement` 버튼 클릭

5-4. `{counter}` 값 -1

3-5. Redux Toolkit

시작하기

  1. terminal에 아래의 명령문 수행

    npm install @reduxjs/toolkit
  2. package.json에서 redux 설정 부분 삭제

  3. store.js 에 아래와 같이 import

    import { createSlice } from "@reduxjs/toolkit";

코드에 적용해보자

  • 기존 react-redux로 reducer funcion을 정의한 코드

    • store/index.js
    const initialState = { counter: 0, showCounter: true };
    
    const counterReducer = (state = { ...initialState }, action) => {
      switch (action.type) {
        case INCREMENT:
          return {
            ...state,
            counter: state.counter + 1,
          };
        case INCREASE:
          return {
            ...state,
            counter: state.counter + action.amount,
          };
        case DECREMENT:
          return {
            ...state,
            counter: state.counter - 1,
          };
        case TOGGLE:
          return {
            ...state,
            showCounter: !state.showCounter,
          };
        default:
          return { ...state };
      }
    };
    
    // create store
    const store = createStore(counterReducer);
    • component/Counter.js
      // useDispatch
      // -> redux store에 대한 action을 전송
      const dispatch = useDispatch();
    
      // useSelector
      //  -> 저장소가 관리하는 데이터에 접근
      //  -> 함수는 react-redux 가 실행
      //     (useSelect를 정의한 컴포넌트에 자동으로 subscribe를 설정함)
      // userSelector(관리된 데이터 => 접근하고자 하는 상태의 부분)
      const counter = useSelector((state) => state.counter);
      const show = useSelector((state) => state.showCounter);
    
      const incrementHandler = () => {
        dispatch({ type: INCREMENT }); // 새로운 action을 보내기 위해 실행
      };
    
      const IncreaseHandler = () => {
        dispatch({ type: INCREASE, amount: 10 });
      };
    
      const decrementHandler = () => {
        dispatch({ type: DECREMENT });
      };
    
      const toggleCounterHandler = () => {
        dispatch({ type: TOGGLE });
      };
  • 같은 코드를 redux-toolkit의 createSlice 를 이용하여 정의

    • store/index.js
    const initialState = { counter: 0, showCounter: true };
    
    // call createSlice
    // createSlice : redux toolkit 내부의 immer에 의해 기존의 상태를 변경시키지 않고 원래 상태를 복제하여 새로운 상태 객체를 생성한다.
    const counterSlice = createSlice({
      name : 'counter',
      initialState, // 초기상태 설정 (별도 지정하지 않으면 initialState의 값을 이용)
      reducers : {
        increment(state) {
          state.counter++;
        },
        decrement(state) {
          state.counter--;
        },
        increase(state, action) {
          state.counter = state.counter + action.payload;
        },
        toggleCount(state) {
          state.showCounter = !state.showCounter;
        }
      }
    });
    
    // create store
    // configureStore : createStore처럼 store생성, 단 여러 개의 리듀서를 하나의 리듀서로 합칠 수 있음(리듀서 병합)
    const store = configureStore({
      reducer: counterSlice.reducer, // 전역 상태를 담당하는 주요 리듀서로 지정
    
      // 여러 개의 리듀서를 쓰는 경우 아래와 같이 쓸 수도 있음
      // reducer: { counter: counterSlice.reducer, select: selectSlice.reducer }
    });
    
    // action생성자: action 객체 생성
    export const counterActions = counterSlice.actions;
    • component/Counter.js
      // useDispatch
      // -> redux store에 대한 action을 전송
      const dispatch = useDispatch();
    
      // useSelector
      //  -> 저장소가 관리하는 데이터에 접근
      //  -> 함수는 react-redux 가 실행
      //     (useSelect를 정의한 컴포넌트에 자동으로 subscribe를 설정함)
      // userSelector(관리된 데이터 => 접근하고자 하는 상태의 부분)
      const counter = useSelector((state) => state.counter);
      const show = useSelector((state) => state.showCounter);
    
      const incrementHandler = () => {
        dispatch(counterActions.increment()); // 새로운 action을 보내기 위해 실행
      };
    
      const IncreaseHandler = () => {
        dispatch(counterActions.increase(10)); // {type : SOME_UNIQUE_IDENTIFIER, payload : 10}
      };
    
      const decrementHandler = () => {
        dispatch(counterActions.decrement());
      };
    
      const toggleCounterHandler = () => {
        dispatch(counterActions.toggleCount());
      };
728x90
반응형

'React' 카테고리의 다른 글

[React] State - 동적 상태 관리 / 생명주기 / 데이터 전달  (0) 2023.07.20
[React] Start React  (0) 2023.07.20