[React] 18. Redux-Saga(api적용)

dev·2020년 10월 23일
0

React

목록 보기
18/21

redux-saga란

redux-saga는 어플리케이션의 사이드 이펙트(데이터 fetch와 같은 비동기 로직이나 브라우저 캐시에 접근하는 것과 같은 순수하지 않은 것들)를 더 효과적으로 관리하려고 만들어졌다. 즉, 효과적으로 실행하고, 쉽게 테스트하고, 쉽게 에러 핸들링을 하자!는 목적으로 만들어졌다.

그래서 saga는 어플리케이션에서 오로지 사이드 이펙트에만 반응하도록 만들어진 별도 쓰레드와 같다고 할 수 있다. redux-saga는 redux 미들웨어로, 보통의 리덕스 액션으로 시작되고, 중단되며, 취소될 수 있다. 또한 redux 어플리케이션의 모든 상태 값에 접근할 수 있고, redux 액션들을 dispatch할 수도 있다.

redux-saga는 비동기 플로우를 쉽게 읽고, 쓰고, 테스트할 수 있도록 ES6의 Generator라는 개념을 사용한다. 이 Generator를 차용한 덕분에 비동기 코드가 마치 스탠다드한 동기 코드처럼 보여진다. redux-thunk와 다르게 콜백 지옥에 빠질 일도 없고, 비동기 로직을 쉽게 테스트할 수 있으며, 액션들을 순수한 상태로 둘 수 있다.

일단 Generatior 문법부터 알아보도록 하겠습니다.
이 문법의 핵심 기능은 함수를 작성 할 떄 함수를 특정 구간에 멈춰놓을 수도 있고, 원할 때 다시 돌아가게 할 수도 있습니다. 그리고 결과값을 여러번 반환 할 수도 있습니다.

제네레이터 함수 예제를 보겠습니다.

function* generatorFunction() {
    console.log('안녕하세요?');
    yield 1;
    console.log('제너레이터 함수');
    yield 2;
    console.log('function*');
    yield 3;
    return 4;
}

제너레이터 함수는 function 뒤에 *를 붙이고, ex)function*
입니다.
제너레이터 함수를 호출한다고 해서 해당 함수 안의 코드가 바로 시작되지는 않습니다. generator.next() 를 호출해야만 코드가 실행되며, yield를 한 값을 반환하고 코드의 흐름을 멈춥니다.

코드의 흐름이 멈추고 나서 generator.next() 를 다시 호출하면 흐름이 이어서 다시 시작됩니다.

제너레이터 함수를 이해 하셨으면 이제 redux-saga를 사용하는 방법을 알보도록 하겠습니다.

index.js

index.js에서 redux-saga를 사용하겠다고 import를 시켜주고, 사가 미들웨어를 만들어주고 store를 생성 할 때 미들웨어에 추가를 시켜줍니다.

import createSagaMiddleware from 'redux-saga';// redux-saga 추가
const sagaMiddleware = createSagaMiddleware(); // 사가 미들웨어를 만듭니다
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(sagaMiddleware)));

그리고 이 다음에 루트 사가를 실행을 시켜줍니다.

sagaMiddleware.run(rootSaga); // 사가를 실행해줍니다.

이렇게 작성하면 index.js에 추가해야되는 부분을 작성이 되었습니다.
이제 사가를 실행시킨 rootSaga를 보도록 하겠습니다.

rootSaga.js

import { combineReducers } from 'redux';
import counter, {counterSaga,counterSaga2 } from './counter';
import { all } from 'redux-saga/effects'
const rootReducer = combineReducers({ counter });
export function* rootSaga() {
yield all([counterSaga(),counterSaga2()]); // all은 배열안의 여러 사가를 동시에 실행시킨다.
}
export default rootReducer;

rootSaga를 보시면 function * rootSaga() 제너레이터 함수가 있습니다.

yield all([counterSaga(),counterSaga2()]);
부분은 counterSaga, counterSaga2 두개를 실행 시켜줍니다. 그렇단 말은 saga로 작동하는 함수가 2개가 있다는 뜻이겠죠

사가들이 존재하는 counter.js를 보도록하겠습니다.

counter.js

import { call, delay, put, takeEvery, takeLatest } from 'redux-saga/effects';
import * as API from '../api/posts';
// 액션 타입
const INCREASE = 'INCREASE';
const DECREASE = 'DECREASE';
const INCREASE_ASYNC = 'INCREASE_ASYNC';
const DECREASE_ASYNC = 'DECREASE_ASYNC';
const GET_NUMBER = 'GET_NUMBER'; // 요청 시작
const GET_NUMBER_S = 'GET_NUMBER_S' // 성공
const GET_NUMBER_F = 'GET_NUMBER_F' // 실패
// 액션 생성 함수
export const increase = (ten) => ({ type: INCREASE, number : ten});
export const decrease = (one) => ({ type: DECREASE, number : one});
export const increaseAsync = (ten) => ({ type :  INCREASE_ASYNC, number : ten});
export const decreaseAsync = (one) => ({type : DECREASE_ASYNC, number : one});
export const getNumber = () => ({type: GET_NUMBER});
export const getNumberS = () => ({type: GET_NUMBER_S});
export const getNumberF = () => ({type: GET_NUMBER_F});
// 제너레이터 함수
export function* counterSaga() {
  yield takeEvery(INCREASE_ASYNC, increaseSaga); // 모든 INCREASE_ASYNC 액션을 처리
  yield takeEvery(GET_NUMBER, numberSaga);
}
export function* counterSaga2() {
  yield takeLatest(DECREASE_ASYNC, decreaseSaga); //가장 마지막으로 디스패치된 DECREASE_ASYNC 액션만
}
function* numberSaga(){
  try{
    const number = yield call(API.getNumber);
    debugger;
    // call 을 사용하면 특정 함수를 호출하고, 결과물이 반환 될 때까지 기다려줄 수 있습니다.
    yield put({type: GET_NUMBER_S, number : number[0]});
  }
  catch(e){
    yield put({type:GET_NUMBER_F, error:true, payload: 0});
  }
}
function* increaseSaga(ten) {
  yield delay(1000); // 1초를 기다립니다.
  if(ten.type === INCREASE_ASYNC){
    yield put(increase(ten.number));
  }
  else{
    yield put(decrease(ten.number));
  }
}
function* decreaseSaga(one) {
  yield delay(1000);
  yield put(decrease(one.number));
}
//takeEvery는 특정 액션 타입에 대하여 디스패치되는 모든 액션들을 처리
//takeLatest는 특정 액션 타입에 대하여 디스패치된 가장 마지막 액션만을 처리
const initialState = 40;
export default function counter(state = initialState, action) {
  switch (action.type) {
    case INCREASE:
      return state + action.number;
    case DECREASE:
      return state - action.number;
    case GET_NUMBER:
      return state;
    case GET_NUMBER_S:
      const a = action.number;
      debugger;
      return {...state, state: action.number};
    case GET_NUMBER_F:
      return state;
    default:
      return state;
  }
}

posts.js

import axios from 'axios';
// 포스트 목록을 가져오는 비동기 함수
export const getNumber = async () => {
  //await sleep(500); // 0.5초 쉬고
  //return posts; // posts 배열
  const response = await axios.get(`http://localhost:4000/number`);
  debugger;
  return response.data;

};

CounterContainer.js

import React, {useEffect} from 'react';
import Counter from '../components/Counter';
import { useSelector, useDispatch } from 'react-redux';
import { increase, decrease, increaseAsync, decreaseAsync, getNumber } from '../modules/counter';
function CounterContainer() {
const number = useSelector(state => state.counter);
debugger;
const dispatch = useDispatch();
const onIncrease = () => {
dispatch(increaseAsync(10));
};
const onDecrease = () => {
dispatch(decreaseAsync(1));
};
useEffect(() => {
dispatch(getNumber());
}, [dispatch]);
return (

);
}
export default CounterContainer;

Counter.js

import React from 'react';
function Counter({ number, onIncrease, onDecrease }) {
  debugger;
  return (
    <div>
      <h1>{number.state}</h1>
      <button onClick={onIncrease}>+10</button>
      <button onClick={onDecrease}>-1</button>
    </div>
  );
}
export default Counter;

data.json

{ 
    "number": [
      50
    ]
  }

코드를 작성했다면 cmd창에서 json-server를 실행 시켜줍니다.

npx json-server ./data.json --port 4000

이렇게 되면 data.json파일은 port 4000으로 가상서버거 만들어 졌습니다.
이제 실행해서 확인을 해보도록 하겠습니다.

profile
studying

0개의 댓글