Redux-saga #2

sangha__ju·2020년 9월 10일
0

Api 요청 상태 관리하기

redux-saga 를 사용하여 api 요청을 해 보자.

우선 api 를 받아올 공간을 생성하자 src 에 lib 폴더를 생성하고 api.js 를 만든다.

// src/lib/api.js

import Axios from "axios";

export const getPost = id =>
    Axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`);

export const getUsers = id =>
    Axios.get(`https://jsonplaceholder.typicode.com/users`);

modules 디렉터리에 sample.js 생성한다.

//modules/sample.js

import { createAction, handleActions } from 'redux-actions';
import { put, call, takeLatest } from 'redux-saga/effects';
import * as api from '../lib/api';
import createRequestSaga from '../lib/createRequestSaga';

const GET_POST = 'sample/GET_POST';
const GET_POST_SUCCESS = 'sample/GET_POST_SUCCESS';
const GET_POST_FAILURE = 'sample/GET_POST_FAILURE';

const GET_USERS = 'sample/GET_USERS';
const GET_USERS_SUCCESS = 'sample/GET_USERS_SUCCESS';
const GET_USERS_FAILURE = 'sample/GET_USERS_FAILURE';

export const getPost = createAction(GET_POST, id => id);
export const getUsers = createAction(GET_USERS);

function* getPostSaga(action) {
  yield put(startLoading(GET_POST)); // 로딩 시작
     // 파라미터로 action 을 받아오면 액션의 정보를 조회 할 수 있다.
     try {
         // call 을 사용하면 Promise 를 반환하는 함수를 호출하고 기다릴 수 있다.
         // 첫 번째 파라미터는 함수, 나머지 파라미터는 해당 함수에 넣을 인수이다.
         const post = yield call(api.getPost, action.payload); // api.getPost(action.payload)
         yield put({
             type: GET_POST_SUCCESS,
             payload: post.data
         });
     } catch(e) {
         // try/catch 를 이용하여 에러를 잡는다.
         yield put({
             type: GET_POST_FAILURE,
             payload: e,
             error: true
         });
     }
     yield put(finishLoading(GET_POST)); // 로딩 완료
}

 function* getUsersSaga() {
     yield put(startLoading(GET_USERS));
     try {
         const users = yield call(api.getUsers);
         yield put({
             type: GET_USERS_SUCCESS,
             payload: users.data
         });
     } catch(e) {
         yield put({
             type: GET_USERS_FAILURE,
             payload: e,
             error: true
         });
     }
     yield put(finishLoading(GET_USERS));
 }

export function* sampleSaga() {
    yield takeLatest(GET_POST, getPostSaga);
    yield takeLatest(GET_USERS, getUsersSaga);
}

// 초기 상태를 선언한다.
// 요청의 로딩 중 상태는 loading 이라는 객체에서 관리한다.

const initialState = {
    post: null,
    users: null
}

const sample = handleActions(
    {
        [GET_POST_SUCCESS]: (state, action) => ({
            ...state,
            post: action.payload
        }),
        [GET_USERS_SUCCESS]: (state, action) => ({
            ...state,
            users: action.payload
        })
    },
    initialState
);

export default sample;

여기서 GET_POST 액션의 경우에는 api 요청을 할 때 어떤 id 로 조회할지 정해 주어야 한다.
redux-saga 를 사용 할 때는 id처럼 요청에 필요한 값을 액션의 payload 로 넣어 주어야 한다.

예를 들어 지금 같은 상황이라면 다음과 같은 액션 객체가 디스패치 된다.

{
  type: 'sample/GET_POST',
  payload: 1
}

그러면 이 액션을 처리하기 위한 saga 를 작성 할 때 payload 값을 api 를 호출하는 함수의 인수로
넣어 주어야 한다.

api 를 호출해야 하는 상황에는 saga 내부에서 직접 호출하지 않고 call 함수를 이용한다.
call 함수의 경우 첫 번째 인수는 호출하고 싶은 함수이고, 그 뒤에 오는 인수들은 해당 함수에
넣어주고 싶은 인수이다.

지금 getPostSaga 의 경우 id 를 의미하는 action.payload 가 인수가 된다.

이제 sampleSaga 를 루트 saga 에 등록한다.

//modules/index.js

import { combineReducers } from "redux";
import counter, { counterSaga } from "./counter";
import todos from "./todos";
import sample from './sample';
import loading from './loading';
import { all } from "redux-saga/effects";
import { sampleSaga } from "./sample";

const rootReducer = combineReducers({
    counter,
    todos,
    sample,
    loading
});

export function* rootSaga() {
    // all 함수는 여러 사가를 합쳐주는 역할
    yield all([counterSaga(), sampleSaga()]);
}

export default rootReducer;

등록 한 후 App 컴포넌트에서 렌더링 해본다.

리팩토링

이제 반복되는 코드를 따로 함수화하여 리팩토링 해 보자.

lib 디렉터리에 createRequestSaga.js 를 생성한다.

// lib/createRequestSaga.js

import { put, call } from 'redux-saga/effects';
import { startLoading, finishLoading } from '../modules/loading';

export default function createRequestSaga(type, request) {
    const SUCCESS = `${type}_SUCCESS`;
    const FAILURE = `${type}-FAILURE`;

    return function*(action) {
        yield put(startLoading(type)); // 로딩 시작
        try {
            const response = yield call(request, action.payload);
            yield put({
                type: SUCCESS,
                payload: response.data
            })
        } catch(e) {
            yield put({
                type: FAILURE,
                payload: e,
                error: true
            });
        }
        yield put(finishLoading(type)); // 로딩 종료
    };
}

이런 식으로 실행 함수를 따로 분리하게 되면 짧은 코드로 구현 할 수 있다.

// modules/sample.js

import { createAction, handleActions } from 'redux-actions';
import { takeLatest } from 'redux-saga/effects';
import * as api from '../lib/api';
import createRequestSaga from '../lib/createRequestSaga';

const GET_POST = 'sample/GET_POST';
const GET_POST_SUCCESS = 'sample/GET_POST_SUCCESS';

const GET_USERS = 'sample/GET_USERS';
const GET_USERS_SUCCESS = 'sample/GET_USERS_SUCCESS';

export const getPost = createAction(GET_POST, id => id);
export const getUsers = createAction(GET_USERS);

const getPostSaga = createRequestSaga(GET_POST, api.getPost);
const getUsersSaga = createRequestSaga(GET_USERS, api.getUsers);

export function* sampleSaga() {
    yield takeLatest(GET_POST, getPostSaga);
    yield takeLatest(GET_USERS, getUsersSaga);
}

// 초기 상태를 선언한다.
// 요청의 로딩 중 상태는 loading 이라는 객체에서 관리한다.

const initialState = {
    post: null,
    users: null
}

const sample = handleActions(
    {
        [GET_POST_SUCCESS]: (state, action) => ({
            ...state,
            post: action.payload
        }),
        [GET_USERS_SUCCESS]: (state, action) => ({
            ...state,
            users: action.payload
        })
    },
    initialState
);

export default sample;
profile
NexCloud(Nexclipper) FrontEnd Developer / Personal Learning Storage

0개의 댓글