[Redux-Saga]Generator

yoosg·2020년 5월 1일
0

비동기 액션을 처리하기 위한 Redux-Middleware중 하나인 Redux-Saga에 대해 알아보자.

redux-saga는 일반 액션을 디스패치하고, 특정 액션이 발생하면 이를 모니터링해서 추가적인 작업을 하도록 설계한다. saga는 Generator라는 문법을 사용하기 때문에 우선 이 문법에 대한 이해가 필요하다.

Generator

핵심은 함수를 작성 할 때 함수를 특정 구간에 머물게 하거나, 원하는 시점에 다시 돌아가게 할 수 있다. 또한 결과값을 여러번 반환 할 수도 있다.

function weirdFunction() {
  return 1;
  return 2;
  return 3;
  return 4;
  return 5;
}

일반적인 함수에서 값을 여러번에 걸쳐 반환하는 것은 불가능하고, 위 함수는 호출 시 1만을 반환 할 것이다.

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

제너레이터 함수를 작성할 때 function* 키워드를 사용한다.
제너레이터 함수를 호출하면 객체가 반환되고, 이를 제너레이터라고 부른다.

const generator = generatorFunction(); // 제너레이터 생성

제너레이터 함수를 호출한다고 해서 해당 함수 안의 코드가 바로 시작되지는 않는다. generator.next() 를 호출해야만 코드가 실행되며, yield를 한 값을 반환하고 코드의 흐름을 멈추게 된다.

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

next 를 호출 할 때 인자를 전달하여 이를 제너레이터 함수 내부에서 사용 할 수도 있다.

function* sumGenerator() {
    console.log('sumGenerator이 시작됐습니다.');
    let a = yield;
    console.log('a값을 받았습니다.');
    let b = yield;
    console.log('b값을 받았습니다.');
    yield a + b;
}

Generator 액션 모니터링

redux-saga는 액션을 모니터링 할 수 있다. Generator를 통해 모니터링이 이루어지는 것을 코드를 통해 확인해보자.

function* watchGenerator() {
    console.log('모니터링 시작!');
    while(true) {
        const action = yield;
        if (action.type === 'HELLO') {
            console.log('안녕하세요?');
        }
        if (action.type === 'BYE') {
            console.log('안녕히가세요.');
        }
    }
}

const watch = watchGenerator();

이런 원리로 액션을 모니터링하고, 특정 액션이 발생했을때 그에 맞는 자바스크립트 코드를 실행시켜준다.

Redux-Saga Counter App

  • action creator & reducer
  • saga (redux-saga에서 제너레이터 함수를 saga라고 한다.)
  • put, takeEvery, takeLatest (redux-saga/effects 유틸 함수)
  • rootSaga 생성

actions / actions.js

import { delay, put } from 'redux-saga/effects';

// 액션 생성 함수
export const increase = () => ({ type: 'INCREASE' });
export const decrease = () => ({ type: 'DECREASE' });
export const increaseAsync = () => ({ type: 'INCREASE_ASYNC' });
export const decreaseAsync = () => ({ type: 'DECREASE_ASYNC' });

// saga
function* increaseSaga() {
  yield delay(1000); // 1sec delay
  yield put(increase()); // action dispatch
}
function* decreaseSaga() {
  yield delay(1000); // 1sec delay
  yield put(decrease()); // action dispatch
}

// redux-saga/effects util func
export function* counterSaga() {
  yield takeEvery('INCREASE_ASYNC', increaseSaga); // 모든 INCREASE_ASYNC 액션을 처리
  yield takeLatest('DECREASE_ASYNC', decreaseSaga); // 가장 마지막으로 디스패치된 DECREASE_ASYNC 액션만 처리
}

reducers / counter.js

// 초기값
const initialState = 0;

export default function counter(state = initialState, action) {
  switch (action.type) {
    case 'INCREASE':
      return state + 1;
    case 'DECREASE':
      return state - 1;
    default:
      return state;
  }
}

reducers / index.js

import { combineReducers } from 'redux';
import counter from './counter';
import { counterSaga } from './actions.js
import posts from './posts';
import { all } from 'redux-saga/effects';

const rootReducer = combineReducers({ counter, posts });

// rootSaga
export function* rootSaga() {
  yield all([counterSaga()]); // all 은 배열 안의 여러 사가를 동시에 실행시켜줍니다.
}

export default rootReducer;

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import rootReducer, { rootSaga } from './modules';
import logger from 'redux-logger';
import { composeWithDevTools } from 'redux-devtools-extension';
import ReduxThunk from 'redux-thunk';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
import createSagaMiddleware from 'redux-saga';

const customHistory = createBrowserHistory();
const sagaMiddleware = createSagaMiddleware(); // 사가 미들웨어 생성.

const store = createStore(
  rootReducer,
  composeWithDevTools(
    applyMiddleware(
      ReduxThunk.withExtraArgument({ history: customHistory }),
      sagaMiddleware, // 사가 미들웨어 적용
      logger
    )
  )
); 

sagaMiddleware.run(rootSaga); // 루트 사가 실행.
//스토어 생성이 된 다음에 위 코드를 실행해야한다.

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

App.js

import React from 'react';
import { Route } from 'react-router-dom';
import CounterContainer from './containers/CounterContainer';

function App() {
  return (
    <>
      <CounterContainer />
    </>
  );
}

export default App;

0개의 댓글