나의 React 개발기록/React

[Redux-Saga] 리덕스에 사가를 함께 써보자! 리덕스 사가 콘솔에 기본 코드 찍어보며 확인하기 !

써머름 2022. 11. 29. 14:28
728x90
반응형

이 전 글에서는 redux를 써야하는 이유와, 기본적인 구독의 개념, 리듀서 생성하여 state 를 변경해주는 법 , 호출함수를 통해 화면에 그려주는 법까지 알아보았다! 이번 글에서는 reduxjs에서 다루기에 까다로운 비동기 작업들을 처리해주는 redux-saga와 함께 써보도록 하자!  

 

- redux-saga를 함께 쓰면, 액션을 모니터링 하고있다가 발생했을때 특정 작업을 하는 방식으로 사용할 수 있다. 

- redux-saga의 장점
1. 비동기 작업에서 기존 요청을 취소 처리 할 수 있다.

2. 특정 액션에 발생했을때, 다른 액션을 디스패치 하거나, 자바스크립트 코드를 실행 할 수 있다.

3. 웹소켓 라이브러리 사용시 Channel이라는 기능을 사용가능하다. 

4. API요청이 실패했을 때 재요청하는 작업을 할 수 있다. 

 

하지만, Generator 문법을 사용하기 때문에 진입장벽이 높다. ( function* ... yield.. 너무 생소했다 ㅜ )

제너레이터 함수를 사용하여 함수에서 값을 순차적으로 반환하고, 함수의 흐름을 도중에 멈춰놓았다가 나중에 이어서 진행할 수 있기 때문에 반드시 필요한 문법이라고 할 수 있다! 

Generator문법의 작동방식을 먼저 이해하고 진행해보자 !

제너레이터 함수 생성 후 .next()로 순차적 진행하는 모습

 

next()의 인자값으로 제너레이터 함수 내부에서 사용가능하다!
제너레이터 함수를 호출하여 제너레이터를 만들고, next()로 타입에 따라 호출하는 모습

리덕스 사가에서는 이러한 원리로 액션을 모니터링하고, 특정 액션이 발생했을때 원하는 자바스크립트 코드를 실행한다! 

리덕스 사가에서는 제너레이터 함수를 "사가" 라고 부른다 ! 

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

// 액션 타입
const INCREASE = 'INCREASE';
const DECREASE = 'DECREASE';
const INCREASE_ASYNC = 'INCREASE_ASYNC';
const DECREASE_ASYNC = 'DECREASE_ASYNC';

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

function* increaseSaga() {
  yield delay(1000); // 1초를 기다립니다.
  yield put(increase()); // put은 특정 액션을 디스패치 해줍니다.
}
function* decreaseSaga() {
  yield delay(1000); // 1초를 기다립니다.
  yield put(decrease()); // put은 특정 액션을 디스패치 해줍니다.
}

// 초깃값 (상태가 객체가 아니라 그냥 숫자여도 상관 없습니다.)
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;
  }
}

여기서 사용한 put이라는 함수는 새로운 액션을 dispatch할 수 있도록 해주는 redux-saga/effects의 유틸함수 이다! 

또 다른 액션을 모니터링 해주는 유틸함수에는 takeEvery, takeLatest가 있는데 ,

takeEvery는 특정 액션 타입에 대하여 디스패치되는 모든 액션을 처리한다.

takeLatest는 특정 액션 타입에 대하여 디스패치된 가장 마지막 액션만을 처리하는 함수이다.

(-> 한 액션을 처리하는 동안 같은 타입의 새로운 액션이 디스패치되면 기존에 하던 작업을 무시하고 새로운 작업을 한다) 

 

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

// 액션 타입
const INCREASE = 'INCREASE';
const DECREASE = 'DECREASE';
const INCREASE_ASYNC = 'INCREASE_ASYNC';
const DECREASE_ASYNC = 'DECREASE_ASYNC';

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

function* increaseSaga() {
  yield delay(1000); // 1초를 기다립니다.
  yield put(increase()); // put은 특정 액션을 디스패치 해줍니다.
}
function* decreaseSaga() {
  yield delay(1000); // 1초를 기다립니다.
  yield put(decrease()); // put은 특정 액션을 디스패치 해줍니다.
}

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

// 초깃값 (상태가 객체가 아니라 그냥 숫자여도 상관 없습니다.)
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;
  }
}

counterSaga 함수의 경우 다른곳에서 불러와서 사용해야 하기 때문에 export 키워드를 사용했다 .

그 다음 나오는 개념은, 프로젝트에서 여러개의 사가를 만들어야 하기 때문에 이를 모두 합쳐놓은 루트사가를 만들어야 한다 ! 

index.js의 설정 ! (리덕스 스토어에 redux-saga 미들웨어를 적용하는것이다)

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
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,
  // logger 를 사용하는 경우, logger가 가장 마지막에 와야합니다.
  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')
);

serviceWorker.unregister();

그리고 app.js에서 CounterContatiner를 렌더링해보자 ! 

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

function App() {
  return (
    <>
      <CounterContainer />
      <Route path="/" component={PostListPage} exact={true} />
      <Route path="/:id" component={PostPage} />
    </>
  );
}

export default App;

여기까지 예시 코드의 흐름이 이해가 되었다면,, redux-saga에서 프로미스를 다루는 방법을 배우러 가보자 ! 

728x90
반응형