이 글은 인프런 제로초님의 '[리뉴얼] React로 NodeBird SNS 만들기' 를 바탕으로 정리한 내용입니다.
const INCREMENT_COUNTER = 'INCREMENT_COUNTER'
// 1 : 동기 액션 크리에이터
function increment() {
return {
type: INCREMENT_COUNTER
}
}
// 2 : 비동기 액션 크리에이터
function incrementAsync() {
return dispatch => {
setTimeout(() => {
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(increment())
}, 1000)
}
}
원래 redux는 비동기 액션이 실행되지 않지, redux-thunk 을 사용하면 실행 가능하다.
하나의 액션에서 디스패치를 여러번 할 수 있다. 하나의 비동기 액션 안에 여러가지 동기 액션을 넣을 수 있다. 원래 리덕스 기능에서 확장된 개념이라고 보면 된다.
const thunk = createThunkMiddleware() as ThunkMiddleware & {
withExtraArgument<
ExtraThunkArg,
State = any,
BasicAction extends Action = AnyAction
>(
extraArgument: ExtraThunkArg
): ThunkMiddleware<State, BasicAction, ExtraThunkArg>
}
// Attach the factory function so users can create a customized version
// with whatever "extra arg" they want to inject into their thunks
thunk.withExtraArgument = createThunkMiddleware
export default thunk
소스코드를 보면 매우 간단하다. 다음의 코드를 바로 프로젝트에 활용해도 되지만 일단 설치해보자.
npm i redux-thunk
미들웨어는 항상 3단 고차함수 형태를 지닌다.
function createThunkMiddleware<
State = any,
BasicAction extends Action = AnyAction,
ExtraThunkArg = undefined
>(extraArgument?: ExtraThunkArg) {
// Standard Redux middleware definition pattern:
// See: https://redux.js.org/tutorials/fundamentals/part-4-store#writing-custom-middleware
const middleware: ThunkMiddleware<State, BasicAction, ExtraThunkArg> =
({ dispatch, getState }) =>
next =>
action => {
// The thunk middleware looks for any functions that were passed to `store.dispatch`.
// If this "action" is really a function, call it and return the result.
if (typeof action === 'function') {
// Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
return action(dispatch, getState, extraArgument)
}
// Otherwise, pass the action down the middleware chain as usual
return next(action)
}
return middleware
}
const loggerMiddleware = ({ dispatch, getState }) => (next) => (action) => {
console.log(action)
return next(action)
}
action 을 실행하기 전에 콘솔 로그를 찍어주는 아주 간단한 미들웨어이다.
const loggerMiddleware = ({ dispatch, getState }) => (next) => (action) => {
console.log(action)
return next(action)
}
const configureStore = () => {
const middlewares = [thunkMiddleware, loggerMiddleware];
const enhancer = process.env.NODE_ENV === 'production'
? compose(applyMiddleware(...middlewares))
: composeWithDevTools(applyMiddleware(...middlewares))
const store = createStore(reducer, enhancer);
return store;
};
const wrapper = createWrapper(configureStore, {
debug: process.env.NODE_ENV === 'development',
});
export default wrapper;
middlewares 안에다가 loggerMiddleware 를 다음의 형식으로 넣어주면 된다.
const middlewares = [thunkMiddleware, loggerMiddleware];
마치 리덕스 데브 툴에서 로깅해주는 것처럼 콘솔에 찍히는 모습을 확인할 수 있다.
커스텀하게 loggerMiddleware 를 만들어서 redux-dev-tools 을 대체할 수도 있다. 리덕스 데브퉅이 동작하는 것 역시 데브툴 미들웨어가 있기 때문이다.
대부분의 요청들은 비동기이다. 모든 기록들이 서버로 한번 갔다가 제대로 처리되면서 실패했으면 failure/ 성공하면 succes 이다.
원칙적으로는 3단계가 있다.
export const loginRequestAction = (data) => {
return {
type: 'LOG_IN_REQUEST',
data
}
}
export const loginSuccessAction = (data) => {
return {
type: 'LOG_IN_SUCCESS',
data
}
}
export const loginFailureAction = (data) => {
return {
type: 'LOG_IN_FAILURE',
data
}
}
thunk는 다음의 방식으로 활용할 수 있다. 10줄이 끝이다. 한번에 dispatch 를 여러번 할 수 있는 게 다이다. 나머지 것들은 다 직접 구현해야 한다.
export const loginAction = (data) => {
return (dispatch, getState) => {
const state = getState();
dispatch(loginRequestAction());
axios.post('/api/login')
.then((res) => {
dispatch(loginSuccessAction(res.data))
})
.catch((err) => {
dispatch(loginFailureAction(err));
})
}
}
Redux-saga 를 사용하면 딜레이 기능을 사용할 수 있다. saga 에서 미리 만들어줘서 제공해준다. 사용자의 실수로 클릭을 두번 했을 경우가 발생할 수 있는데 thunk의 경우 2번 모두 요청이 날라가지만 saga에서 take latest 를 이용하면 가장 나중에 발생한 요청 1개만 보낼 수 있다.
셀프 디도스를 막기 위해서는 saga의 throttle(스크롤 방지..) 를 사용하여 1초에 3번 이상 액션이 일어날 경우 차단할 수 있다.
Q. 미들웨어 사용 이유?
리듀서에서 처리하기 전에 다른 일을 하고자 미들웨어를 쓴다. 리듀서는 무조건 동기 작업만 수행할 수 있습니다. (태생적으로) 그래서 비동기 작업을 수행하기 위해서 thunk같은 미들웨어를 액션과 리듀서 사이에 끼어넣어야 한다.
Q. 리액트에서 비동기를 위해 thunk, saga는 필수인가 ?
직접 하셔도 되지만, 컴포넌트에서 axios를 호출하는 것은 지양하시는 게 좋다. 그렇게 하다보면 모양이 thunk나 saga처럼 구현되므로 처음부터 thunk나 saga를 쓰는게 좋다.