본문 바로가기
SPA/React

[React] 함수형 컴포넌트에서 Redux-Saga 사용하기

by J4J 2021. 7. 26.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 함수형 컴포넌트에서 Redux-Saga 사용하는 방법에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

Redux 미들웨어

 

redux-saga에 대해 알기 전 redux 미들웨어의 개념에 대해 인지를 하고 있어야 합니다.

 

redux 미들웨어라고 하는 것은 action함수들이 reducer에 전달될 때 전달되는 action함수들을 가로채어 부가 작업들을 진행하는 장소를 일컫습니다.

 

 

 

redux 미들웨어를 사용하는 목적으로 알고 있는 것들은 다음과 같은 것들이 있습니다.

 

  • 비동기 처리
  • redux 로그 처리
  • 하나의 action함수로 다수의 action함수 실행

 

 

 

그리고 이런 redux 미들웨어 중 대표적으로 사용되는 라이브러리는 다음과 같습니다.

 

  • redux-thunk
  • redux-saga

 

 

 

저는 redux-thunk를 사용해보지 않아서 redux-saga와 명확한 비교를 할 수는 없습니다.

 

다만 redux-thunk와 redux-saga의 큰 차이점 중 하나로 알고 있는 것은 action함수를 가로채가는 시기입니다.

 

redux-thunk 같은 경우는 reducer에 action함수가 도달하기 이전에 가로채갑니다.

 

그렇기 때문에 하나의 action함수를 사용하여 비동기 처리 + reducer 처리를 한 번에 해낼 수 없습니다.

 

하지만 redux-saga는 reducer에 action함수가 도달된 이후에 가로채갑니다.

 

결국 하나의 action함수만 사용하더라도 비동기 처리 + reducer 처리를 한 번에 이뤄낼 수 있게 됩니다.

 

 

 

Redux-Saga

 

redux-saga는 es6버전에 등장한 generator를 사용하여 구현을 합니다.

 

여기서 generator라고 하는 것은 함수의 실행을 멈추고 원하는 시점에 함수가 실행될 수 있도록 도와줍니다.

 

generator함수를 선언하는 방법은 function* 키워드를 이용하여 선언할 수 있습니다.

 

 

 

redux-saga에서 사용되는 여러 문법들이 존재하는데 구현도 할 거면서 대표적으로 사용되는 문법들만 간단히 소개드리겠습니다.

 

  • all → generator 함수를 배열의 형태로 넣어주면 동시에 실행
  • call → 비동기 함수를 호출할 때 사용
  • put → reducer에 특정 action함수를 전달
  • takeEvery → action함수를 감지하여 지정된 작업을 처리, 작업이 완료되기 전에 다시 감지될 시 그에 맞는 또 다른 작업을 처리
  • takeLatest → action 함수를 감지하여 지정된 작업을 처리, 작업이 완료되기 전에 다시 감지될 시 기존 진행되던 작업은 종료하고 새로운 작업을 처리

 

그리고 이런 문법들은 모두 yield라는 키워드를 가지고 사용할 수 있습니다.

 

 

 

Redux-Saga 설정

 

설정해보기에 앞서 redux를 사용할 때 활용할 수 있는 미들웨어이기 때문에 기본적으로 redux환경이 구성되어야 합니다.

 

redux환경이 구성되어 있지 않으신 분들은 여기를 참고해주시면 됩니다.

 

 

반응형

 

 

그리고 redux-saga 설정이 정상적으로 이루어졌는지는 redux환경을 구성할 때 만들어 둔 값이 증가되는 액션을 이용하여 테스트해보겠습니다.

 

비동기 처리는 다음과 같은 api를 호출해보겠습니다.

 

호출 API

 

 

[ 1. 패키지 설치 ]

 

$ npm install redux-saga
$ npm install axios // axios 설치 안하신 분들만
$ npm install babel-polyfill // async/await을 현재 사용 안하시고 계시는 분들만

 

 

 

[ 1.5. webpack 설정 파일에 polyfill 적용 ]

 

해당 부분은 async/await을 사용안하고 계셔서 위의 패키지 설치 때 babel-polyfill을 설치하신 분들만 해당되는 구간입니다.

 

webpack설정 파일의 entry부분에 보통 "./src/index"로 설정되어 있을 텐데 다음과 같이 변경해주시면 됩니다.

 

entry: ["babel-polyfill", "./src/index"],

 

 

 

[ 2. redux-saga 구현 파일 생성 ]

 

action함수를 가로채어 미들웨어 처리가 이루어지는 redux-saga 구현 파일을 생성해보겠습니다.

 

위치는 /src/modules에 numberSaga.jsx로 생성하겠습니다.

 

import { call, put, takeEvery } from 'redux-saga/effects'
import axios from 'axios';
import { decreaseAction, increase } from './actions';

const getMyInfo = (age) => {
    // 여기서 age는 action.payload값
    return axios.get('http://localhost:8080/rest/myInfo', {
        params: {
            age: age,
        }
    });
}

function* increaseActionSaga(action) {
    const res = yield call(getMyInfo, action.payload) // action함수의 파라미터를 api의 파라미터로 전달
    /*
    // getMyInfo를 따로 만들지 않을 경우
    const res = yield call([axios, 'get'], 'http://localhost:8080/rest/myInfo', {
        params: {
            age: action.payload,
        }
    })
    */

    if(res.data) { // api 호출 성공 시
        console.log(res.data); // 데이터 출력
        yield put(decreaseAction(1)); // store에 저장되어 있는 num을 1만큼 감소
    }
}

const numberSaga = [
    takeEvery(increase, increaseActionSaga),
    // sagaAction1,
    // sagaAction2,
    // ...
];

export default numberSaga;

 

 

 

[ 3. saga들을 묶어주는 파일 생성 ]

 

reducer와 마찬가지로 saga들을 묶어주는 파일도 생성해보겠습니다.

 

위치는 /src/modules에 indexSaga.jsx로 생성하겠습니다.

 

import { all } from "redux-saga/effects";
import numberSaga from "./numberSaga";

function* indexSaga() {
    yield all([
        ...numberSaga,
        // saga1,
        // saga2,
        // ...
    ])
}

export default indexSaga;

 

 

 

[ 4. index.jsx에 미들웨어 등록 ]

 

기존에 redux를 위해 구현되어 있던 코드는 다음과 같습니다.

 

import * as React from 'react';
import ReactDom from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import indexReducer from './modules/indexReducer';

const store = createStore(indexReducer, composeWithDevTools());

ReactDom.render(
    <Provider store={store}>
        {/* BrowserRouter를 사용할 경우 프로그래밍 되는 위치 */}
        <App />
    </Provider>, 
    document.querySelector('#root')
);

 

 

728x90

 

 

그리고 saga등록을 위해 수정되는 코드는 다음과 같습니다.

 

파일 위치는 /src에 index.jsx입니다.

 

import * as React from 'react';
import ReactDom from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import { applyMiddleware, createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import indexReducer from './modules/indexReducer';
import createSagaMiddleware from 'redux-saga';
import indexSaga from './modules/indexSaga';

const sagaMiddleware = createSagaMiddleware();
const store = createStore(indexReducer, composeWithDevTools(applyMiddleware(sagaMiddleware)));
sagaMiddleware.run(indexSaga);

ReactDom.render(
    <Provider store={store}>
        {/* BrowserRouter를 사용할 경우 프로그래밍 되는 위치 */}
        <App />
    </Provider>, 
    document.querySelector('#root')
);

 

 

 

[ 5. App.jsx 파일 ]

 

redux포스팅에서 /src에 App.jsx파일을 다음과 같이 구현해놨었습니다.

 

import * as React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { decreaseAction, increaseAction } from './modules/actions';

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

    const num = useSelector(state => state.numberReducer.num); // indexReducer에 정의해둔 numberReducer의 initState에 정의된 num값 가져오기

    const onClickIncrease = () => {
        dispatch(increaseAction(3)); // dispatch를 이용하여 action함수를 reducer에 전달
    }

    const onClickDecrease = () => {
        dispatch(decreaseAction(2)); // dispatch를 이용하여 action함수를 reducer에 전달
    }

    return (
        <div>
            <p>현재 값: {num}</p>

            <button onClick={onClickIncrease}>증가</button>
            <button onClick={onClickDecrease}>감소</button>
        </div>
    )
}

export default App;

 

 

redux-saga구현이 안되어 있는 경우에는 증가버튼을 누르게 되면 3만큼 값이 증가하고, 감소 버튼을 누르게 되면 2만큼 값이 감소되도록 되어 있었습니다.

 

위의 redux-saga 설정을 모두 적용되었다면 상황이 변경됩니다.

 

증가버튼을 누르게 되면 동일하게 3만큼 값이 증가됩니다.

 

다만 이후에 해당 액션을 redux-saga가 가로챈 뒤 파라미터 값으로 전달받은 3을 api를 호출할 때 사용하고 호출된 api의 결과값을 콘솔 창에 출력합니다.

 

또한 값을 감소시키는 action함수를 reducer에 전달하여 값을 1만큼 감소시키기도 합니다.

 

결론적으로 값은 2만 증가하게 되고 콘솔창에 api결과가 출력되게 됩니다.

 

 

 

감소 버튼을 누르는 경우에는 redux-saga가 가로채도록 설정하지 않았기 때문에 값을 2만큼 감소시키는 동일한 결과가 나옵니다.

 

 

실행 결과

 

 

 

파일 구성

 

파일 구성

 

 

 

참조

 

 

Redux-Saga란?

 

 

 

 

 

 

 

이상으로 함수형 컴포넌트에서 Redux-Saga 사용하는 방법에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글