리덕스의 리듀서는 순수함수이여야 하므로 API 통신을 통한 응답 데이터를 저장하는 등의 기능은 미들웨어를 통해 진행해야합니다.
redux-thunk
도 있지만 기존에 사용해보았던 경험이 있는 redux-saga를 사용하도록 하겠습니다.
리덕스 사가는 리덕스를 통한 비동기 장업 및 브라우저 캐시 액세스 등의 작업을 더 쉽게 관리하고 실행과 테스트를 쉽게하는 것을 목표로 하는 라이브러리입니다.
Generators(이터러블을 생성하는 함수)라는 ES6 기능을 사용해서 비동기 흐름을 읽고 테스트를 할 수 있습니다. 그로인해 콜백 지옥에서 벗어날 수 있는 장점이 있습니다.
여러 함수가 있는데 몇가지만 살펴보겠습니다.
put 함수는 특정 액션을 디스패치해주는 함수입니다.
yield put({ type: ACTION }); // ACTION이라는 액션을 디스패치합니다.
새로운 하위 Saga task를 생성시켜주는 함수입니다.
fork는 블럭되지 않으며 호출 시점에 호출자는 부모 task가 되고 fork된 saga는 자식 task가 되며 부모 task가 취소되면 자식 task도 취소됩니다.
call은 블럭되는 fork
라고 보면 된다. 인자로 함수나 saga task를 받을 수 있습니다.
두번째부터의 인자는 첫번째 인자인 함수나 task에 인자로 들어갈 데이터들입니다.
보통 Promise 등의 실행(보통은 Ajax Call)에 쓰이며 Promise가 resolve될 때까지 블럭됩니다.
takeEvery는 지정한 액션의 모든 디스패치를 처리해줍니다.
takeLatest는 지정한 액션 중 가장 마지막으로 디스패치된 액션을 처리해줍니다.
Saga의 배열을 인자로 받으며 인자로 받은 요소인 사가들을 동시에 실행시켜주는 함수입니다.
yield all([saga1(), saga2(), saga3(), ... ]);
간단하게 서버에 GET 요청을 전송해서 응답으로 포스트를 전송받는 saga를 작성해보았습니다.
// sagas/posts.tsx
import { loadCategoriesAsync, loadPostsAsync } from '@reducers/posts';
import axios from 'axios';
import { call, all, fork, takeLatest, put } from 'redux-saga/effects';
async function loadAllPostsAPI(query: any) {
return await axios.get(`/post`);
}
function* loadAllPosts(action: ReturnType<typeof loadPostsAsync.request>) {
try {
const result = yield call(loadAllPostsAPI, action.payload);
yield put(loadPostsAsync.success(result.data));
} catch (error) {
console.error(error);
yield put(loadPostsAsync.failure(error));
}
}
function* watchLoadAllPosts() {
yield takeLatest(loadPostsAsync.request, loadAllPosts);
}
export default function* postsSaga() {
yield all([fork(watchLoadAllPosts)]);
}
작성한 사가를 sagas/index.tsx
파일에 작성한 rootSaga
에서 동시에 실행시키겠습니다.
// sagas/index.tsx
import axios from 'axios';
import { all, call } from 'redux-saga/effects';
import posts from './posts';
export default function* rootSaga() {
yield all([call(user), call(post), call(posts)]);
}
그리고 기존에 작성했던 store/configureStore.tsx
의 configureStore
함수에 미들웨어를 추가시키고 실행시키는 코드를 추가해줍시다.
// store/configureStore.tsx
...
const configureStore = () => {
const sagaMiddleware = createSagaMiddleware();
const middlewares = [sagaMiddleware];
const enhancer =
process.env.NODE_ENV === 'production'
? compose(applyMiddleware(...middlewares))
: composeWithDevTools(applyMiddleware(...middlewares));
const store = createStore(reducers, enhancer);
store.sagaTask = sagaMiddleware.run(sagas);
return store;
};
...
에러가 발생한 것처럼 보이지만 redux-saga에서 발생하는 것이 아니라 서버로의 데이터 요청 부분에서 발생하는 에러를 받아온 것으로 연결은 정상적으로 된 것으로 보입니다.