본문 바로가기

프론트엔드/React

[React] Redux + Saga, API호출 해보기

* 본 글은 Redux와 React에 대한 기본 지식이 필요합니다

 

 

사이드 프로젝트로 택배 조회 서비스를 개발하고 있습니다.

 

이번 프로젝트에 처음으로 Redux Saga를 도입했습니다.

 

Redux Saga를 사용한 Api 호출을 알려드리고 싶어서 글을 작성합니다.

 

저는 Delivery Tracker Open Api를 사용했습니다.


목차

  • 기본 세팅
  • action 작성
  • reducer 작성
  • saga 작성
  • store, middleware 세팅
  • 실제 코드에서 사용해보기

기본 세팅을 해줍시다.

 

Redux와 Redux Saga를 설치합니다.

 

npm install redux redux-saga

// or

yarn add redux redux-saga

 

설치를 한 후 action, reducer, saga파일을 작성할 modules폴더를 생성합니다.

 

modules 폴더 안에는

  • action 파일들을 모을 actions 폴더
  • reducer를 모을 reducers 폴더
  • saga파일을 모을 sagas 폴더

를 생성합니다.


제일 먼저 action 파일을 생성합니다.

 

src/modules/actions/courierAction.js

export const loadCourier = () => {
    return{
        type: "LOAD_COURIER"
    };
};

export const loadCourierSuccess = couriers => {
    return{
        type: "LOAD_COURIER_SUCCESS",
        couriers: couriers
    };
};

export const loadCourierFail = error => {
    return{
        type: "LOAD_COURIER_FAIL",
        error
    };
};

loadCourier 함수는 추후 rootSaga 제너레이터 함수를 작성할 때 필요합니다.

 

saga 제너레이터 함수를 작성할 때 알아봅시다.

 

action 함수들을 한 곳으로 모아줄 allAction파일을 작성해봅시다.

 

src/modules/actions/index.js

import { loadCourier, loadCourierFail, loadCourierSuccess } from './courierAction';

const allAction = {
    loadCourier,
    loadCourierFail,
    loadCourierSuccess
};

export default allAction;

아래에서 rootSaga를 작성할 때 action을 불러오기 위해 사용합니다.


reducer 파일도 작성해 봅시다.

 

src/modules/reducers/courierReducer.js

const couriers = (state = [], action) => {
    switch(action.type){
        case "LOAD_COURIER_SUCCESS":
            return [...state, ...action.couriers];
        case "LOAD_COURIER_FAIL":
            return [...state, action.error];
        default:
            return state;
    }
};

export default couriers;

 

이 후 index.js 에서 combineReducer로 reducer를 묶어줍니다.

 

src/modules/reducers/index.js

import couriers from './courierReducer';
import { combineReducers } from 'redux';

const rootReducer = combineReducers({
    couriers
});

export default rootReducer;

여기까지는 일반 Redux 작성 법과 같습니다!


Saga 파일을 작성해 봅시다.

 

action 파일 생성 때 말했던 rootSaga를 작성해봅시다.

 

api에서 택배사 정보를 불러오기 위해 api를 받아오는 코드 먼저 작성해보겠습니다.

 

src/lib/api/index.js

import axios from 'axios';
import { BASE_URL } from '../../config/config.json';

const searchCourier = () => {
    return axios.get(
        `${BASE_URL}/carriers`
    );
};

const api = {
    searchCourier
};

export default api;

 

 

src/modules/sagas/index.js

import { takeEvery, put, call } from 'redux-saga/effects';
import api from '../../lib/api/index';
import allAction from '../actions/index';

function* getCourier() {
    console.log("택배사 불러오기 성공");
    try{
        const { data } = yield call(api.searchCourier);
        console.log(data);
        yield put(allAction.loadCourierSuccess(data));
    }catch(error){
        yield put(allAction.loadCourierFail(error));
    }
}

function* rootSaga(){
    yield takeEvery("LOAD_COURIER", getCourier);
}

export default rootSaga;

제너레이터 함수로 rootSaga를 만들었습니다.

 

takeEvery는 Redux Saga의 이펙트 중 하나입니다.

dispatch에 의해 action.type이 "LOAD_COURIER'인 객체가 올 때 getCourier를 실행시키라는 의미입니다.

 

getCourier에서는 call로 API를 호출하고 put으로 dispatch 합니다.

 

여기서 call 함수의 인자는 Promise를 반환해야 합니다.

때문에 api/index.js 파일에서 작성했다시피 axios.get()을 리턴하면 처리가 잘 됩니다.

 

이렇게 비동기로 받은 데이터를 put, 즉 dispatch 하는 것 입니다.


이제 미들웨어를 store에 추가하겠습니다.

 

 

src/index.js 

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootReducer from './modules/reducers';
import rootSaga from './modules/sagas';
import * as serviceWorker from './serviceWorker';

const sagaMiddleware = createSagaMiddleware();

const store = createStore(
  rootReducer,
  compose(
    applyMiddleware(sagaMiddleware),
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  )
);

sagaMiddleware.run(rootSaga);

ReactDOM.render(
  <Provider store = {store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

serviceWorker.unregister();

createSagaMiddleware() 함수로 미들웨어를 생성합니다.

그 후 store에 applyMiddleware를 넣어줍니다.

 

compose 안에 window... 구문은 리덕스 개발 툴을 사용하기 위해 작성한 것입니다.

Redux Devtools를 모르신다면 제가 작성한 글을 보시는 것을 추천드립니다.( [React] Redux DevTools 사용하기 )

 

이 후 미들웨어에서 run 함수를 실행합니다.

이것은 마치 이벤트 리스터를 열어서 rootSaga에 해당하는 액션이 올 때를 기다리는 것과 같습니다.

 

이렇게 Redux Saga를 사용하면 Redux를 사용할 때와는 다르게 두 가지가 추가됩니다.

  • createSagaMiddleware() 로 미들웨어 생성
  • 미들웨어에 run 함수 실행

이제 사용해봅시다!

 

src/pages/Home.js

import React, { useEffect } from 'react';
import '../styles/Home/Home.scss';
import { useDispatch, useSelector } from 'react-redux';
import allAction from '../modules/actions/index';

const Home = () => {
  const result = useSelector(state => state.couriers);
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(allAction.loadCourier());
  }, []);

  return (
    <div className = "HomeContainer">
      <div className = "HomeMainContainer">
     	
      </div>
    </div>
  );
}

export default Home;

액션 생성 함수를 만들 때 loadCourier()의 역할이 여기서 나옵니다.

 

여기서, useEffect는 컴포넌트가 마운트 됐을 때 아래 코드를 실행합니다.

dispatch({ type: "LOAD_COURIER" });

saga 미들웨어가 존재하기 때문에 dispatch가 rootSaga로 넘어갑니다.

그 안에서 takeEvery()를 만나 type이 일치하는 것이 확인되고 getCourier로 넘어갑니다.

 

여기선 비동기로 로직을 실행한 다음에 put으로 action의 loadCourierSuccess()를 dispatch합니다.

마지막으로 리듀서로 넘어가 state가 업데이트 됩니다!


Redux Saga는 처음에 이해하는 과정은 어렵지만, 이해하고 나면 사용하기 쉬운 것 같습니다.

 

여러분만의 예제를 통해 새롭게 개발하면서 Redux Saga를 익혀보세요!

 

사용하기 정말 쉬우니까 여러분도 사용해보세요!

 

참고 문헌 및 사이트

 

[React] Redux + Saga, 상태관리를 끝장내버렸다.

React의 새로운 패러다임, React Hooks

Redux Saga 공식문서

 

위의 코드 혹은 프로젝트 전체 코드를 확인하고 싶으신 분은 아래의 링크를 통해 확인해주세요😊

Delivery Helper Github

 

긴 글 읽어주셔서 감사합니다😀