redux는 전역 state를 효율적으로 관리하고 공유하기 위해서 사용한다면, redux-saga
는 서버를 통해 받아오는 데이터같은 비동기적인 동작들을 효율적으로 관리하기 위해 사용
한다고 한다.
saga와 관련된 파일들을 관리하기 위해 modeuls 폴더 하위에 sagas 폴더를 생성하였다.
어제 학습했던 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;
여기서 가장 중요하게 봐야 할 부분은 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);
}
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;
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);
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에 도달한다고 한다.