본문 바로가기

project

[React] 인스타 클론코딩 기록 (3) - redux-saga, REST API

미들웨어는 redux가 비동기 액션을 dispatch 할 수 있도록 합니다.

이전 글의 코드 부분에 middlewares가 있었는데 이 부분이 비동기를 디스패치를 해주는 역할을 합니다.

 

const loggerMiddleware = ({ dispatch, getState }) => (next) => (action) => {
    console.log(action);
    return next(action);
};

여기서 미들웨어는 dispatch가 실행될 때 마다 동작하고 action은 dispatch가 일어날 때의 action입니다.

next는 다음 미들웨어를 호출하는 역할을 합니다. 다음 미들웨어가 없으면 dispatch 됩니다.

위의 loggerMiddleware 는 액션을 실행할 때 마다 콘솔이 찍히도록 되어있습니다.

 

저는 redux-saga를 이용해 미들웨어를 다뤄보려 합니다.

npm i redux-saga next-redux-saga axios

redux-saga와 next-redux-saga, axios를 설치합니다.

 

configureStore.js 에

import createSagaMiddleware from 'redux-saga';

const loggerMiddleware = ({ dispatch, getState }) => (next) => (action) => {
    console.log(action);
    return next(action);
};

const configureStore = () => {
    const sagaMiddleware = createSagaMiddleware();
    const middlewares = [sagaMiddleware, loggerMiddleware];
    const enhancer = process.env.NODE_ENV === 'production'
        ? compose(applyMiddleware(...middlewares))
        : composeWithDevTools(
            applyMiddleware(...middlewares),
        );
    const store = createStore(reducer, enhancer);
    store.sagaTask = sagaMiddleware.run(rootSaga);
    return store;
};

추가해 줍니다.

여기에 store.sagaTask 는 이후 서버사이드렌더링을 위해 사용될 친구입니다.

 

pages/_app.js 에도

import withReduxSaga from 'next-redux-saga';

불러와 주시고 export에

export default wrapper.withRedux(withReduxSaga(App));

withReduxSaga로 한 번 더 감싸줍니다.

 

 

saga는 제너레이터 함수를 사용합니다. 제너레이터 함수는 중단점이 있는 함수입니다.

yield를 통해 중단점을 만들 수 있고 멈출 때는 next를 하지 않으면 됩니다.

const gen = function* () {
	console.log(1);
    yield;
    console.log(2);
    yield 3;
}

const generator = gen();

generator.next()
// 1

generator.next()
// 2

generator.next()
// 3

무한반복문에서 유용하게 중단점을 만들 수 있는 함수입니다. 이 성질을 saga가 활용합니다.

 

루트에 sagas 폴더를 만들어 index.js를 만들어 줍니다.

로그인 액션을 예시로 동작을 이해해 봅니다.

import { all, fork, takeLatest, call, put } from 'redux-saga/effects';
import axios from 'axios';

function logInAPI(data) {
	return axios.post('/api/login', data);
}

function* logIn(action) {
    try {
        const result = yield call(logInAPI, action.data);
        yield put({
            type: 'LOG_IN_SUCCESS',
            data: result.data   //성공결과
        });
    } catch (err) {
        yield put({
            type: 'LOG_IN_FAILURE',
            data: err.response.data //실패 결과
        })
        console.error(err);
        
    }
	
}

function* watchLogin() {
    yield takeLatest('LOG_IN_REQUEST', logIn);
}

export default function* rootSaga() {
    yield all([
       fork(watchLogin), 
    ]);
}

rootSaga의 all은 그 배열 안에 있는 것들을 실행해 줍니다. 여기서는 fork를 적었지만 fork나 call을 통해 위의 함수가 실행 됩니다.

 

1) watchLogin을 실행하면 yield takeLatest('LOG_IN_REQUEST')을 만나게 됩니다. 여기서 takeLatest는 'LOG_IN'이 실행될 때까지 기다려 주고 동시에 동일한 요청이 여러번 들어올 경우, 마지막 요청만 처리합니다.(서버로 요청을 전부 보내지만 처리는 마지막 한번만)

2) 'LOG_IN_REQUEST' 이후, 이제 logIn이 실행되면 logInAPI를 실행하고 그 return 값이 data라는 인자를 통해 을 result에 action.data의 형태로 담습니다.

3) logInAPI를 실행해서 data를 서버에 요청을 보냅니다.

4) logIn()은 try, catch 를 통해 비동기 액션의 서버 전송 성공, 실패의 경우를 나눕니다.

 

함수 실행에서 fork와 call의 차이는

fork: 비동기 함수 호출

call: 동기 함수 호출

 

위의 logIn()의 경우,

call(logInAPI)가 return할 때 까지 기다려서 result에 값을 넣는데

fork(logInAPI)는 결과 기다림과 상관 없이 다음 줄로 넘어가 버립니다.

이 경우에는 꼭 call을 써야합니다.

 

저렇게 한 동작에 대한 세트가 완성~

 

또 api 요청(위의 axios.post 등)에 post, get, patch, delete 등이 있는데 이건 후에 백엔드와 함께 데이터 요청에 중요한 역할을 합니다.

RESTful API 라고 하는 원칙적인 웹의 사용방법? 한 마디로 표현하기 참 난해하긴 합니다.

RESTful API를 사용하기 위해 HTTP Method(GET, POST, PATCH, PUT, DELETE)를 통해 요청을 보내고 응답을 받습니다.

 

GET /post/1 : 1번 post(게시글) 정보를 가져온다.

POST /user/2 : 2번 user 정보를 작성(생성)한다.

PATCH /user/name/ : 해당 유저의 정보 중 name을 변경한다.

PUT /post/count/1 : 1번 게시글의 count(조회수) 정보 모두 변경한다. (=> 조회수가 1 늘어나므로 조회수 전체가 변경 됨)

DELETE /post/2 : 2번 게시글을 삭제한다.

 

이런 식으로 작성되데 주소만 봐도 알기 쉽고 관리도 쉽고 HTTP를 사용하는 다른 플랫폼에서도 재사용이 가능하고 캐싱도 되는 장점이 아주 많군요.

 

물론 꼭 지키기 어려운 경우도 종종 생기는 것 같지만 의미있는 API를 위해 다른 개발자분들과 협력, 소통이 중요한 부분입니다.