리덕스(Redux)

JOY🌱·2023년 4월 26일
0

🐳 React

목록 보기
5/5
post-thumbnail

Github

💁‍♀️ 리덕스(Redux)란,
상태 관리를 하기 위한 라이브러리
앱의 상태 전부는 하나의 저장소(store) 안에 있는 객체 트리에 저장 됨.
상태 트리를 변경하는 유일한 방법은 어떤 행동이 일어날지에 해당하는 action이며, 이 action에 따라 상태를 어떻게 변경할지를 명시하기 위한 함수를 작성하고 이를 reducer함수라고 함


🙋‍ 사용 시, 장점

  • 애플리케이션의 구조가 단순해짐
  • 상태 관리 로직을 테스트하기 쉬워지며, 디버깅 용이

🤔❓ REDUX 실행 순서
useEffect내에서 dispatch(func)
👉 middleware(redux-thunk) : func 호출
👉 dispatch(aciton)
👉 middleware(redux-thunk) : action 객체 넘기기
👉 middleware(redux-logger)
👉 reducer 호출
👉 반환 된 state가 store에 저장


📌 createActions / handleActions 참고

createActions

/* action */
const INCREMENT = 'count/INCREASE';
const DECREMENT = 'count/DECREASE';
/* createActions 사용 
여러 개의 액션 함수를 한 번에 생성 가능. 접두사가 있기 대문에 대괄호를 이용해서 키 식별자를 작성.
전달하는 객체의 key 값이 action의 type 속성 값이 되고, value 값의 함수가 반환하는 값이 payload의 속성 값이 됨
action type 속성 값에 따라 반환 되는 객체의 키 값이 설정 됨 EX) actions.count.increment */
const { count : { increase, decrease } } = createActions({ // 구조분해할당으로 count 안에 있는 increment, decrement를 꺼내기
	[INCREMENT] : (amount = 1) => ({ incrementValue : amount }), // key : value 형태로 선언
	[DECREMENT] : (amount = 1) => ({ decrementValue : amount })
});

handleActions

/* reducer */
/* reducer 함수도 handleActions 기능을 사용하여 간결하게 작성 가능
첫 번째 인자로 state를 전달 받고 두 번째 인자로 action을 전달 받음
중첩 구조분해할당({ payload : { incrementValue })으로 payload 값을 꺼내 콜백 함수 안에서 사용 가능
dispatch 호출 시 전달 된 action의 type과 일치하는 함수를 동작시키게 됨 */
const reducer = handleActions({
	// type(key) : 콜백함수(value)
	[INCREMENT] : (state, { payload : { incrementValue } }) => { return state + incrementValue; },
	[DECREMENT] : (state, { payload : { decrementValue } }) => { return state + decrementValue; }
}, initialState); // handleActions의 두 번째 인자로 다루는 state의 초기값 설정

👀 MenuList, Menu 조회

1) dispatch()로 API호출하는 함수 호출

useDispatch useSelector

👉 MenuList

import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { callGetMenuListAPI } from "../../API/MenuAPICall";
import MenuItem from "../items/MenuItem";


function MenuList() {

    const dispatch = useDispatch();
    const result = useSelector(state => state.menuReducer); // 변화된 state안의 값을 꺼내옴
    const menuList = result.menulist;

    console.log('result : ', result);

    useEffect(
        () => {
            /* menuList 호출 API */
            dispatch(callGetMenuListAPI()); // MenuAPICall에 작성한 함수 호출하여 retuen되는 함수 자체를 받아옴
        },[]
    )

    return (
        <div className="menuBox">
            { menuList && menuList.map(menu => <MenuItem key={ menu.id } menu={ menu }/>) }
        </div>
    );
}

export default MenuList;

👉 Menu

import { useDispatch, useSelector } from "react-redux";
import { callGetMenuAPI } from "../../API/MenuAPICall";
import { useEffect } from "react";

function Menu({ id }) {

    const dispatch = useDispatch();
    const result = useSelector(state => state.menuReducer); /* 꺼내고 싶은 state를 useSelector를 이용하여 꺼내기 */
    const menu = result.menu;

    console.log('result : ', result);

    useEffect(
        () => {
            /* Menu 호출 API */
            dispatch(callGetMenuAPI(id));
        },[]
    );

    return (
        <>
            { menu && 
                <>
                    <h3>메뉴 이름 : { menu.menuName }</h3>
                    <h3>메뉴 가격 : { menu.menuPrice }</h3>
                    <h3>메뉴 종류 : { menu.categoryName }</h3>
                    <h3>메뉴 상세 : { menu.detail.desciption }</h3>
                    <img src={ menu.detail.image } style={{ maxWidth : 500 }} alt={ menu.menuName } />
                </>
            }
        </>
    );
}

export default Menu;

💁‍♀️ 코드 분석

  • useDispatch()는 컴포넌트 내부에서 action creator 함수를 dispatch할 때 사용
    - dispatch 함수는 해당 action을 store로 보내서 reducer 함수를 실행
  • useSelector()는 store의 state에 접근할 수 있게 해주는 함수
    • 이 함수는 전체 store의 state를 인자로 받아서 필요한 값을 추출하여 반환
    • 이를 통해 컴포넌트가 필요한 데이터만을 추출할 수 있게 함
  • useSelector()를 사용해서 menuReducer에서 필요한 menulist를 추출하여 menuList 변수에 할당하고 이를 활용하여 menuList 데이터를 화면에 렌더링

2) dispatch()로 action객체 전달

👉 Api

미리 axios 라이브러리를 이용하여 서버로 HTTP 요청을 보내는 함수를 정의
axios async await

import axios from 'axios';

const DOMAIN = 'http://localhost:4000';

export const request = async (method, url, data) => {
    return await axios({
        method,
        url : `${DOMAIN}${url}`,
        data
    })
    .then(res => res.data)
    .catch(error => console.log(error));

}

👉 MenuAPICall

dispatch()로 action객체 전달하며 reducer함수 호출

import { getMenu, getMenulist } from "../modules/MenuModule";
import { request } from "./Api"


/* 서버로부터 메뉴 목록을 가져오는 API를 호출 */
export function callGetMenuListAPI() {

    console.log('getMenuList api calls...📞')

    /* 반환되는 비동기 처리 함수가 dispatch의 매개변수로 전달되게 되면 redux-thunk라는 미들웨어에서 아래 함수가 호출되게 됨 */
    return async (dispatch, getState) => {

        /* axios 라이브러리를 이용한 데이터 요청 */
        // Api.js 파일에 있는 request함수에 보낼 때 매개변수 => (요청 방식, 도메인 뒤에 붙을 url, 같이 보낼 data가 있다면 data)
        const result = await request('GET', '/menu') 
        console.log('getMenuList result : ', result);

        /* dispatch의 매개변수로 action 객체를 전달하여 store에 state를 저장하게 함 (리듀서 함수 호출) */
        dispatch(getMenulist(result));

    }
}
/* 서버로부터 id와 일치하는 메뉴를 가져오는 API를 호출 */
export function callGetMenuAPI(id) {

    console.log('getMenu api calls...📞')

    return async (dispatch, getState) => {

        const result = await request('GET', `/menu/${ id }`);
        console.log('getMenu result : ', result);

        dispatch(getMenu(result));
    }

}

💁‍♀️ 코드 분석
위 Api 코드는 axios 라이브러리를 이용하여 서버로 HTTP 요청을 보내는 함수 request를 정의하는 코드.

  • request 함수는 매개변수로 HTTP 요청 방식(method), 요청할 URL(url), 그리고 요청 데이터(data)를 받음. axios 라이브러리의 axios() 메소드를 이용하여 서버에 HTTP 요청을 보내고, 이 메소드는 Promise 객체를 반환.
  • Promise 객체는 비동기 처리를 위한 객체로, .then() 메소드와 .catch() 메소드를 이용하여 비동기 작업이 완료될 때까지 기다렸다가 결과 값을 받아 처리할 수 있음.
  • 위 코드에서는 .then() 메소드를 이용하여 서버로부터 받은 응답 데이터(res.data)를 반환하고, .catch() 메소드를 이용하여 오류 발생 시 콘솔에 에러를 출력.
  • 👉 request 함수는 매개변수로 전달된 method, url, data를 이용하여 axios 라이브러리를 이용해 서버에 HTTP 요청을 보내고, 응답 데이터를 반환하는 함수.

위 MenuAPICall 코드는 서버로부터 메뉴 목록을 가져오기 위해 API를 호출하는 함수인 callGetMenuListAPI()를 정의하는 코드.

  • callGetMenuListAPI() 함수는 async 키워드를 사용하여 비동기 처리 함수를 반환하며, 반환되는 함수는 redux-thunk 미들웨어에서 호출. 이 함수의 매개변수로는 dispatchgetState가 전달.
  • 함수 내부에서는 axios 라이브러리를 이용하여 서버로부터 메뉴 목록 데이터를 요청하며, request() 함수를 이용하여 요청을 보내게 됨. 이 때 요청 방식은 GET이며, 요청 URL은 /menu.
  • 데이터 요청이 완료되면, 반환되는 결과값 result를 이용하여 getMenulist() 액션 객체를 생성하고, dispatch() 함수를 이용하여 해당 액션 객체를 전달하여 store에 state를 저장.
  • 이렇게 저장된 state는 리듀서 함수에서 관리.

3) 전달받은 action에 따른 state 변경

👉 MenuModule

import { createActions, handleActions } from 'redux-actions';

/* 초기값 */
const initialState = {};

/* 액션 타입 */
const GET_MENULIST = 'menu/GET_MENULIST';   // 메뉴 리스트 조회
const GET_MENU = 'menu/GET_MENU';           // 메뉴 조회

/* 액션 함수 */
export const { menu : { getMenulist, getMenu } } = createActions({
    [GET_MENULIST] : (res) => ({ menulist : res }), // 응답값(value)을 menulist 라는 key에 담음
    [GET_MENU] : (res) => ({ menu : res })
});

/* 리듀서 함수 */
const menuReducer = handleActions({
    // 액션이 전달되어 오면 타입을 기준으로 함수가 호출되고, 그 반환 값이 store에 저장하고자 하는 state임
    [GET_MENULIST] : (state, { payload }) => {
        return payload;
    },
    [GET_MENU] : (state, { payload }) => {
        return payload;
    }

}, initialState);

/* menuReducer를 export하여 index.js에서 import하여 사용 */
export default menuReducer;

💁‍♀️ 코드 분석

  • 초기값 (initialState): Redux Store에서 사용할 초기값을 정의. 이 값은 state가 undefined일 때 사용.
  • 액션 타입 (action types): Redux Store에서 사용할 액션의 종류를 정의. 액션 타입은 문자열 상수로 정의되며, 액션 객체를 생성할 때 type 프로퍼티로 사용.
  • 액션 함수 (action creators): 액션 객체를 생성하는 함수. 이 함수를 호출하면 액션 객체가 생성되며, 이때 type 프로퍼티는 액션 타입 상수를 사용.
  • 리듀서 함수 (reducer function): 액션을 받아서 이전 상태를 새로운 상태로 변경하는 함수이며, Redux Store의 상태를 변경하기 위해 사용. 액션 타입에 따라 상태를 변경하는 로직이 구현되며, state와 action을 매개변수로 받아서 새로운 state를 반환.

위 코드에서는 createActions 함수를 사용하여 액션 함수를 생성하고, handleActions 함수를 사용하여 리듀서 함수를 작성. createActions 함수는 리덕스 액션 객체를 생성하는 함수를 자동으로 생성해주며, 액션 타입과 액션 생성 함수를 정의할 때 코드 양을 줄일 수 있음. handleActions 함수는 액션 타입과 리듀서 함수를 매핑하여 하나의 리듀서 함수를 생성.

  • menuReducer: 최종적으로 만들어진 리듀서 함수. 액션 타입에 따라 state를 변경하는 로직이 구현되어 있음. 이 함수를 Redux Store의 reducer로 등록하면, 액션에 따라 state가 변경.

위 코드에서 { menu : { getMenulist, getMenu } }는 반환된 객체에서 menu 속성의 값을 가져오는데, 이 값은 GET_MENULIST와 GET_MENU라는 액션 타입으로 생성된 액션 생성 함수를 갖고 있는 객체.
즉, menu 객체는 GET_MENULIST와 GET_MENU 액션 타입에 해당하는 액션 생성 함수를 갖고 있으며, 이 함수들을 getMenulist와 getMenu에 할당. 이렇게 함으로써, 나중에 getMenulist나 getMenu 함수를 호출할 때, menu/GET_MENULIST 또는 menu/GET_MENU와 같이 액션 타입을 지정하여 사용할 수 있음.

4) 여러 개의 reducer 함수 합쳐놓기

👉 Index

import { combineReducers } from 'redux';
import menuReducer from './MenuModule';
import userReducer from './UserModule';

const rootReducer = combineReducers({
    menuReducer, userReducer // 성격이 다른 리듀서 함수를 하나로 묶음 
});

/* rootReducer를 export하여 Store.js에서 import하여 사용 */
export default rootReducer;

5) Store 생성

👉 Store

import { applyMiddleware, legacy_createStore as createStore } from "redux";
import rootReducer from "./modules/Index";
import { composeWithDevTools } from 'redux-devtools-extension';
import ReduxThunk from 'redux-thunk';
import ReduxLogger from 'redux-logger';

/* createStore deprecated 되어 있으나 구동에 문제는 없으며 이름을 명시적으로 legacy_createStore as createStore로 작성하면 사라짐
deprecated 시킨 이후는 향후 @reduxjs/tookit를 이용하여 사용할 것을 명확하게 권고하기 위해서임 */
const store = createStore(
    rootReducer,                                                    // 리듀서 함수 설정
    composeWithDevTools(applyMiddleware(ReduxThunk, ReduxLogger))   // 사용할 미들웨어들 설정
);

export default store;

6) 모든 하위 컴포넌트에서 Redux store를 사용 가능 하도록 설정

👉 index

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import store from './Store';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <Provider store={ store }>
      <App />
    </Provider>
);
profile
Tiny little habits make me

0개의 댓글