리덕스는 기본적으로 동기적인 작업을 수행하는데 중점을 두고 있습니다.
동기적인 작업은 간단하고 직관적이며 상태 변화에 있어서 순차적으로 실행되기 때문에
상태 변화를 추적하기가 쉽습니다.
또한 리덕스를 사용하는 이유 중 하나이자 큰 장점은 리듀서에 의한 멱등성입니다.
멱등성은 동일한 입력에 있어서 항상 동일한 출력을 하는 성질을 말합니다.
그럼 동일한 입력에 있어서 다른 출력을 하는 상황이 무엇일까요?
간단한 예시로 우리가 api 요청을 해서 데이터를 받아오는 과정이 있다고 해봅시다.
여기서만 해도 api 요청의 성공과 실패로 항상 동일한 결과를 반환한다고 기대하긴 어렵습니다.
멱등성을 보장해야하는 리덕스에서 다른 결과를 반환할 수도 있는
비동기처리와 같은 작업을 어떻게 수행해야할까요?
할 때 사용하는 것이 바로 리덕스 미들웨어 입니다.
const middleware = store => next => action => {
// 하고 싶은 작업
}
function 키워드를 사용해서 나타낸다면 다음과 같이 나타낼 수 있습니다.
function middleware(store) {
return function (next) {
return function (action) {
// 실제로 처리하고 싶은 작업을 작성
return next(action);
};
};
};
미들웨어의 구조를 보면 함수에서 함수를 반환하고 있는 모습입니다.
그리고 처리하고 싶은 작업을 마친 뒤에는 next(action) 을 통해
다음 미들웨어 또는 리듀서에게 액션을 전달해야합니다.
store
store 는 리덕스 스토어 인스턴스입니다.
dispatch , getState와 같은 내장 함수들을 가지고 있습니다.
next
next 함수는 액션을 받아 다음 미들웨어에게 전달하는 함수입니다.
이 때 다음 미들웨어가 없다면, 리듀서에게 해당 액션을 전달하게 됩니다.
만약에 next 를 호출하지 않게 된다면 액션이 무시처리되어 리듀서에게로 전달되지 않습니다.
action
action은 현재 처리하고 있는 액션 객체 입니다.
현재 리덕스로 만들어둔 카운터와 투두를 합쳐 하나의 루트리듀서로 스토어에 사용하고 있고,
redux-logger 를 통해 확인할 수 있는 투두 아이템을 추가를 했을 때의 콘솔입니다.
액션을 통해 상태가 업데이트 전과 후, 또 어떠한 액션이 전달되었는지가 한 눈에 보기 쉽게 나와있습니다.
저는 기존에 데브 툴스도 사용하고 있어서 미들웨어를 어디에 추가해야할지 잘 몰랐었는데
이런식으로 사용이 가능하고, 또 한가지 이상의 미들웨어 등록도 가능하다는 것을 알게 되었습니다.
위의 예시를 보면서 next의 동작을 설명하자면,
myLogger > logger 미들웨어가 순차적으로 실행되고, myLogger 에 있는 next 함수를 통해 logger 미들웨어로 action이 전달되는 것입니다.
이후 미들웨어가 존재하지 않기 때문에 액션이 리듀서로 전달될 것입니다.
Thunk 미들웨어
thunk 미들웨어를 사용하면 액션 객체가 아닌 함수를 디스패치 할 수 있습니다.
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
해당 전체코드를 보고 우리는 Thunk 미들웨어를 사용한다면 리덕스 스토어의
dispatch, getState 를 사용할 수 있게 되는 것을 알 수 있고,
action의 타입이 함수라면 dispatch, getState, extraArgument 를 인자로 받아 실행하는 액션 함수를 반환하게 된다는 것을 알 수 있습니다.
그리고 return next(action) 을 통해 알 수 있듯이 다음 미들웨어 또는 리듀서에게 액션을 전달하는 미들웨어임을 알 수 있습니다.
// Thunk 함수 정의
export const increaseAsync = () => (dispatch, getState) => {
setTimeout(() => {
const currentState = getState();
// 현재 상태를 확인하고 이에 따른 처리도 가능합니다.
dispatch(increase());
// 1초 뒤에 액션이 디스패치되는 비동기 처리가 가능
}, 1000);
};
// Thunk 함수 사용
dispatch(increaseAsync());
여기서 사용하고 있는 dispatch 와 getState 는 리덕스 스토어에게 제공받고 있는 함수들입니다.
그리고 해당 함수에서 setTimeout이 실행되어 1초 후에dispatch(increase()) 가 호출되어 함수인 increase 이 디스패치 될 것입니다.
여기서의 핵심은 increaseAsync 함수를 호출하면, 내부에서 비동기 처리를 수행하고 그 결과로 increase 액션을 디스패치할 수 있다는 것입니다.
이런 식으로 Thunk 함수를 사용하면, 특정 시점이나 조건에 따라 액션을 동적으로 처리하는 로직을 쉽게 작성할 수 있습니다.