[React] redux-saga 적용해보기

jiseong·2021년 10월 5일
0

T I Learned

목록 보기
91/291

redux-saga

redux는 전역 state를 효율적으로 관리하고 공유하기 위해서 사용한다면, redux-saga는 서버를 통해 받아오는 데이터같은 비동기적인 동작들을 효율적으로 관리하기 위해 사용한다고 한다.

1) sagas 폴더 생성

saga와 관련된 파일들을 관리하기 위해 modeuls 폴더 하위에 sagas 폴더를 생성하였다.

2) action 정의 및 reducer 생성

어제 학습했던 redux와 동일하게 액션을 정의하고 reducer를 생성한다.

// ./accomodation.js

import { createAction, handleActions } from 'redux-actions';

// 액션 정의
export const GET_ACCOMODATIONLIST = `accomodation/GET_ACCOMODATIONLIST`;
export const GET_ACCOMODATIONLIST_SUCCESS = `accomodation/GET_ACCOMODATIONLIST_SUCCESS`;
export const GET_ACCOMODATIONLIST_FAIL = `accomodation/GET_ACCOMODATIONLIST_FAIL`;

// saga에서 호출하는 액션 객체
export const getAccomodationList = createAction(GET_ACCOMODATIONLIST, location => location);
export const getAccomodationSuccess = createAction(
  GET_ACCOMODATIONLIST_SUCCESS,
  accomodationList => accomodationList,
);
export const getAccomodationFail = createAction(GET_ACCOMODATIONLIST_FAIL, e => e);

const initialState = {
  accomodationList: [],
};

// reducer 생성
const accomodation = handleActions(
  {
    [GET_ACCOMODATIONLIST_SUCCESS]: (state, { payload: accomodationList }) => {
      return {
        ...state,
        accomodationList: [...state.accomodationList, ...accomodationList],
      };
    },
    /* 생략. .. */
  },
  initialState,
);

export default accomodation;

3) saga 생성

여기서 가장 중요하게 봐야 할 부분은 watchAccomodation이다.

takeEvery 함수는 통해 컴포넌트에서 dispatch hook을 사용하여 액션을 dispatch하게 되면 그 액션에 맞는 제네레이터 함수를 실행시키는 함수이다.

그리고 call 함수put 함수가 보이는데
call은 일반 함수를 실행해주고 put은 action을 dispatch 한다고 생각하면 된다.

call(fn, ...args)
함수의 첫 번째 파라미터는 함수, 나머지 파라미터는 해당 함수에 넣을 인수

put(action)
함수의 첫 번째 파라미터로 액션 객체

// ./sagas/accomodationSaga.js

import { call, put, takeEvery } from 'redux-saga/effects';
import { axiosInstance, getAccomodationAPI } from '../../api-config';
import { getAccomodationFail, getAccomodationSuccess, GET_ACCOMODATIONLIST } from '../accomodation';

function* getAccomodationListSaga({ payload: location }) {
  console.log('숙소 찾기 시작...', location);
  try {
    const {
      data: { accomodationList },
    } = yield call(getAccomodationAPI, location);
    yield put(getAccomodationSuccess(accomodationList));
  } catch (e) {
    yield put(getAccomodationFail(e));
  }
}

// GET_ACCOMODATIONLIST 액션이 dispatch 되면 getAccomodationListSaga 제너레이터를 실행한다
export default function* watchAccomodation() {
  yield takeEvery(GET_ACCOMODATIONLIST, getAccomodationListSaga);
}

4) rootSaga 생성

rootReducer와 비슷하게 여러 saga 파일들이 존재 시, rootSaga를 이용하여 배열 안에 작성해줘야 한다.

// ./index.js
import { combineReducers } from 'redux';
import { all } from 'redux-saga/effects';
import accomodation from './accomodation';
import watchAccomodation from './sagas/accomodationSaga';
import searchForm from './searchForm';

const rootReducer = combineReducers({
  searchForm,
  accomodation,
});

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

export default rootReducer;

5) store에 saga 연결

Store와 saga를 연결하기 위해서는 미들웨어를 사용해야 하는데 이는
createSagaMiddle와 applyMiddleware를 사용하면 된다.

// src/index.js

import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { applyMiddleware, createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import createSagaMiddleware from 'redux-saga';
import App from './App';
import rootReducer, { rootSaga } from './modules';

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

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

// 스토어 생성이 된 다음에 호출해야 한다.
sagaMiddleware.run(rootSaga);

6) saga 실행

getAccomodationList
export const getAccomodationList = createAction(GET_ACCOMODATIONLIST, location => location)로 GET_ACCOMODATIONLIST 액션을 실행시킨다.
그렇게되면 saga는 GET_ACCOMODATIONLIST 액션에 대해 반응하여 그에 맞는 제네레이터 함수를 실행시키게 된다.

import { useDispatch, useSelector } from 'react-redux';
import { getAccomodationList } from '../../modules/accomodation';

const dispatch = useDispatch();
const { accomodationList } = useSelector(state => state.accomodation);
const { location } = useSelector(state => state.searchForm);

useEffect(() => {
  dispatch(getAccomodationList(location));
}, []);

💻 결과

saga를 이용하여 axios같은 비동기 처리를 해결 후에 결과에 따른 액션을 호출 할 수 있었다.

saga를 활용하면 다양한 장점이 있다고 하는데 현재로써는 이와 같은 비동기 처리 코드가 많이 존재한다면 제네레이터 방식을 활용한 saga를 쓰는 것이 유용 할 것이라고 생각된다.

의문점

어떤 특정한 액션에 대해서 saga가 반응하게 코드가 작성되어 있을 때, 해당 액션은 reducer에게 도달하는지가 궁금했다.

결론은 saga가 반응하기 전에 먼저 reducer에 도달한다고 한다.


Reference

0개의 댓글