redux-saga

김태완·2022년 2월 9일
2
post-thumbnail

reudx 미들웨어의 끝판왕 redux-saga를 정리해보자.
이 게시글은 7시간 고생하며 겨우 성공한 아주 간단한 counter 예제를 입문자 시점에서 정리한글이다.
이 글만 따라하면 redux-saga의 기본적인 구조와 비동기 실행까지 따라 할 수 있다..
글을 잘 못써서 읽기 힘들어도 천천히 따라해보면,,, 할 수 있다!

예제는 아래 깃허브에서 fork 해서 사용해보기 바란다.
https://github.com/aptakqmf12/redux-saga

우선 Generator 개념부터

  • function뒤에 *이 붙은 형태고 yield는 기존 함수의 return개념과 같은데 next()시 해당지점에 point를 잡고 다음 next()실행시 point지점부터 시작된다 (아래 예제로 확인)
  • next()시 반환값은 객체( {value, done} )형태이다.
funtion* counter(){
	yield 10
    yield 20
    yield 30
}
const gen = conter();
gen.next() // {value : 10, done: false}
gen.next() // {value : 20, done: false}
gen.next() // {value : 30, done: false}
gen.next() // {value : undefined, done: true}

saga에서 사용하게 될 redux-saga 메소드

  1. all : 배열형태로 generator함수들을 넣어주면 해당 함수들이 한꺼번에 실행되고 모두 끝날때(resolve)까지 기다린다.
    yield all([foo01, foo02])

  2. call : 지정한 함수를 동기적으로 실행할때 사용, 첫번째 파라미터는 함수명, 두번째 파라미터는 해당 함수에 전달할 파라미터
    yield call(delay, 1000)

  3. put : 특정액션을 dispatch를 하는 내용
    yield put({type: "INCLEMENT_COUNT"})

  4. takeLatest : 첫번째 파라미터는 받을 액션명, 두번째 파라미터는 액션명을 받으면 실행될 함수. takeEvery와 다르게 실행도중에 다른 액션이 들어오면 해당 실행이 끝날때까지 무시한다
    yield takeLatest("INCLEMENT_COUNT", increment)

  5. takeEvery : takeLatest와 형태가 같고 실행중 다른 액션이 들어와도 모두 수행한다는 차이점이 있다.
    yield takeEvery("INCLEMENT_COUNT", increment)

  6. fork : 함수의 비동기 실행시 사용
    fork(increment())

redux-saga 패턴

redux폴더의 구조

{ redux{ store.js, reducer{index.js, counter.js}, saga{index.js, counter.js} } }

redux-sage의 패턴정리

redux/store.js : 스토어 생성 및 reducer와 saga연결.

import createSagaMiddleware from "@redux-saga/core";
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";

import rootSaga from "./saga/index";
import { rootReducer } from "./reducer/index";

const sagaMiddleware = createSagaMiddleware();

export const store = createStore(
  rootReducer,
  composeWithDevTools(applyMiddleware(sagaMiddleware))
);

sagaMiddleware.run(rootSaga);

redux/reducer/counter.js : 액션 생성과 리듀서 생성.

  • 여기서 COUNT_UP_ASYNC에 대한 액션은 saga에서 처리하기때문에 액션생성은 하지않는다
const INITIAL_STATE = {
  count: 0,
};

export const COUNT_UP_ASYNC = "COUNT_UP_ASYNC";
export const COUNT_UP = "COUNT_UP";
export const COUNT_DOWN = "COUNT_DOWN";

export const countUp = () => {
  return {
    type: COUNT_UP,
  };
};
export const countDown = () => {
  return {
    type: COUNT_DOWN,
  };
};

export function counterReducer(state = INITIAL_STATE, action) {
  switch (action.type) {
    case COUNT_UP:
      return {
        ...state,
        count: state.count + 1,
      };
    case COUNT_DOWN:
      return {
        ...state,
        count: state.count - 1,
      };

    default:
      return state;
  }
}

redux/reducer/index.js : reducuer들을 합쳐줌 (userReducer은 예제엔 없는데 추가적인 reducer가 생겼을때 저렇게 합쳐주면된다)

import { combineReducers } from "redux";
import { counterReducer } from "./counter";
import { userReducer } from "./user";

export const rootReducer = combineReducers({ counterReducer, userReducer });

redux/saga/counter.js : "COUNT_UP_ASYNC" 라는 액션을 받으면 아래처럼 delay->"COUNT_UP"->delay->"COUNT_UP"이 동기적으로 실행된다!
*또한 takeEvery로 액션을 받았기때문에 액션이 끝나기전에 또 클릭하면 한꺼번에 실행된다.

import { all, put, takeEvery, takeLatest } from "redux-saga/effects";

const delay = (ms) => new Promise((res) => setTimeout(res, ms));

function* countDoubleUp() {
  yield delay(1000);
  yield put({ type: "COUNT_UP" });
  yield delay(1000);
  yield put({ type: "COUNT_UP" });
}

// COUNT_UP_ASYNC액션을 받으면 countDoubleUp를 takeEvery방식으로 실행
function* watchCountDoubleUp() {
  yield takeEvery("COUNT_UP_ASYNC", countDoubleUp);
}

// 위의 watch사가들을 종합적으로 묶어서 export
export default function* counterSaga() {
  yield all([watchCountDoubleUp()]);
}

redux/saga/index.js : reducer처럼 rootSaga에 모든 saga들을 결합해준다.
이때 all 메서드를 사용해서 합쳐준다. (all을 모른다 스크롤을 올려서 처음부터 봐라!)

import { all, call, put, takeLatest } from "redux-saga/effects";
import user from "./user";
import counterSaga from "./counter";

export default function* rootSaga() {
  yield all([counterSaga(), user()]);
}

index.js : Provider로 store 를 넘겨주면 끝~

import { Provider } from "react-redux";
import { store } from "./redux/store";

ReactDOM.render(
  <Provider store={store}>
    <React.StrictMode>
      <App />
    </React.StrictMode>
  </Provider>,
  document.getElementById("root")
);
profile
프론트엔드개발

0개의 댓글