reudx 미들웨어의 끝판왕 redux-saga를 정리해보자.
이 게시글은 7시간 고생하며 겨우 성공한 아주 간단한 counter 예제를 입문자 시점에서 정리한글이다.
이 글만 따라하면 redux-saga의 기본적인 구조와 비동기 실행까지 따라 할 수 있다..
글을 잘 못써서 읽기 힘들어도 천천히 따라해보면,,, 할 수 있다!
예제는 아래 깃허브에서 fork 해서 사용해보기 바란다.
https://github.com/aptakqmf12/redux-saga
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}
all : 배열형태로 generator함수들을 넣어주면 해당 함수들이 한꺼번에 실행되고 모두 끝날때(resolve)까지 기다린다.
yield all([foo01, foo02])
call : 지정한 함수를 동기적으로 실행할때 사용, 첫번째 파라미터는 함수명, 두번째 파라미터는 해당 함수에 전달할 파라미터
yield call(delay, 1000)
put : 특정액션을 dispatch를 하는 내용
yield put({type: "INCLEMENT_COUNT"})
takeLatest : 첫번째 파라미터는 받을 액션명, 두번째 파라미터는 액션명을 받으면 실행될 함수. takeEvery와 다르게 실행도중에 다른 액션이 들어오면 해당 실행이 끝날때까지 무시한다
yield takeLatest("INCLEMENT_COUNT", increment)
takeEvery : takeLatest와 형태가 같고 실행중 다른 액션이 들어와도 모두 수행한다는 차이점이 있다.
yield takeEvery("INCLEMENT_COUNT", increment)
fork : 함수의 비동기 실행시 사용
fork(increment())
{ redux{ store.js, reducer{index.js, counter.js}, saga{index.js, counter.js} } }
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
: 액션 생성과 리듀서 생성.
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")
);