ⓐ 각각의 미들웨어에 { dispatch, getState } 객체를 전달하고 실행 후, 각각을 chain 배열에 할당
dispatch, getState가 담긴 객체 middlewareAPI를 전달해 실행하게 되므로 middleware는 아래와 같은 형태가 됨.
// middleware1은 클로저를 이루기 때문에 dispatch, getState를 지속적으로 참조함
const dispatch = storeAPI.dispatch;
const getState = storeAPI.getState;
// middleware1 === middleware(middlewareAPI)
function middleware1(next) {
return function (action) {
if (typeof action === "function") {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
ⓑ 합성 함수에 dispatch()를 전달해 실행
현재 미들웨어로 thunkMiddleware 하나만 마운트되어 있으므로 middleware1(store.dispatch)로 Store의 dispatch()가 reference 형태로 전달됨. 만약, 둘 이상의 미들웨어가 있다면 마지막 미들웨어 f3이 store.dispatch를 전달받아 f2, f1 순서로 합성되고, dispatch될 때는 반대로 f1 → f2 → f3 순으로 실행됨.
// middleware1은 클로저를 이루기 때문에 dispatch, getState를 지속적으로 참조함
const dispatch = storeAPI.dispatch;
const getState = storeAPI.getState;
// middleware2 === middleware(middlewareAPI)(store.dispatch)
function middleware2(action) {
if (typeof action === "function") {
return action(dispatch, getState, extraArgument);
}
return store.dispatch(action);
};
ⓒ ⓐ~ⓑ에서 만든 함수를 새로운 dispatch()로 만듦
applyMiddleware() 마지막 부분을 보면 이렇게 만든 middleware2를 Store의 dispatch로 재할당하게 됨.
Thunk 미들웨어를 사용했을 때와 아닐 때, Thunk 함수는 둘 다 동일하지만 Thunk 함수를 사용하는 방법이 다름!
※ redux-toolkit은 redux-thunk의 thunkMiddleware를 기본 미들웨어로 장착하고 있기 때문에 별도로 thunkMiddleware를 마운트할 필요가 없음.
5. 기타
A. compose 함수
// redux/src/compose.js
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
// 함수가 하나만 전달되었다면 해당 함수만 반환
if (funcs.length === 1) {
return funcs[0]
}
// 둘 이상의 함수를 합성
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
compose()는 함수의 배열 funcs의 함수들을 왼쪽부터 모두 합성한 함수를 반환하는 함수. 만약, applyMiddleware(f1, f2, f3, ...)처럼 여러개의 함수(middleware)를 전달했다면, reduce() 메서드를 적용했을 때 아래와 같은 방식으로 합성 함수가 생성됨.
// 첫번째 단계
(x₁) => f1(f2(x₁));
// 두번째 단계
(x₂) => ( (x₁) => f1(f2(x₁)) )(f3(x₂)); // 함수 f3(x₂)이 x₁으로서 전달되어 실행됨
(x₂) => f1(f2(f3(x₂));
따라서, funcs = [f1, f2, f3]이라면 최종적으로 f3 → f2 → f1 순서로 실행됨.
B. 미들웨어의 실행 순서
const f1 = (x) => {
return x - 1;
};
const f2 = (x) => {
return x * 2;
};
compose(f1, f2)(1); // 1
compose()로 함수를 합성했을 때, f2→ f1로 뒤에 있는 함수부터 실행되는 것을 확인할 수 있음. 하지만, Redux에서 미들웨어는 f1 → f2 순서로 실행됨!? 결론부터 말하면 미들웨어가 커링 함수이기 때문에 위 예시와는 다른 순서로 실행됨.
Thunk Middleware의 작동 방식 살펴보기
Thunk 미들웨어(thunkMiddleware)는 위와 같이 Redux Store에 마운트할 수 있는데, 그 첫 과정으로 thunkMiddleware는 아래와 같은 함수에서 반환됨.
1. redux-thunk에서의 thunkMiddleware #
우리가 사용할 Thunk 미들웨어는 마운트되기 전에 아래와 같은 구조를 가짐.
2. Thunk Middleware 마운트: applyMiddleware() #
1에서 만든 Thunk 미들웨어를 사용하기 위해서는 Store에 미들웨어를 마운트해야 하는데 redux에서 제공하는 applyMiddleware()를 이용할 수 있음. applyMiddleware 함수는 아래와 같은 코드로 이루어짐.
3. thunkMiddleware를 마운트했을 때의 dispatch() 함수
미들웨어는 둘 이상 사용할 수 있지만 좀 더 간단하게 이해하기 위해 thunkMiddleware 하나만 사용하는 경우를 생각해 봄.
그 전에 thunkMiddleware에서 반환된 미들웨어 함수의 형태를 기억해두면 이해하기 쉬움.
applyMiddleware()에 thunkMiddleware가 전달된다면,
ⓐ 각각의 미들웨어에 { dispatch, getState } 객체를 전달하고 실행 후, 각각을 chain 배열에 할당
dispatch, getState가 담긴 객체 middlewareAPI를 전달해 실행하게 되므로 middleware는 아래와 같은 형태가 됨.
ⓑ 합성 함수에 dispatch()를 전달해 실행
현재 미들웨어로 thunkMiddleware 하나만 마운트되어 있으므로 middleware1(store.dispatch)로 Store의 dispatch()가 reference 형태로 전달됨. 만약, 둘 이상의 미들웨어가 있다면 마지막 미들웨어 f3이 store.dispatch를 전달받아 f2, f1 순서로 합성되고, dispatch될 때는 반대로 f1 → f2 → f3 순으로 실행됨.
ⓒ ⓐ~ⓑ에서 만든 함수를 새로운 dispatch()로 만듦
applyMiddleware() 마지막 부분을 보면 이렇게 만든 middleware2를 Store의 dispatch로 재할당하게 됨.
4. Thunk Middleware 이용하기
이렇게 만들어진 storeDisptach는 인수로 전달된 action이 객체인지, 함수인지 판단해 dispatch() 실행을 다르게 하는 역할밖에 없어 이러한 미들웨어가 무슨 의미가 있는지 의문이 들 수 있음.
하지만, Store의 dispatch()는 일반 객체만 받을 수 있도록 설계되어있기 때문에 비동기 작업에 대한 Action Creator는 비동기가 아닌 Action Creator와 형태가 달라져 Store에 접근할 때 의도치 않은 실수를 할 수 있음.
Thunk 미들웨어를 사용했을 때와 아닐 때, Thunk 함수는 둘 다 동일하지만 Thunk 함수를 사용하는 방법이 다름!
※ redux-toolkit은 redux-thunk의 thunkMiddleware를 기본 미들웨어로 장착하고 있기 때문에 별도로 thunkMiddleware를 마운트할 필요가 없음.
5. 기타
A. compose 함수
compose()는 함수의 배열 funcs의 함수들을 왼쪽부터 모두 합성한 함수를 반환하는 함수. 만약, applyMiddleware(f1, f2, f3, ...)처럼 여러개의 함수(middleware)를 전달했다면, reduce() 메서드를 적용했을 때 아래와 같은 방식으로 합성 함수가 생성됨.
따라서, funcs = [f1, f2, f3]이라면 최종적으로 f3 → f2 → f1 순서로 실행됨.
B. 미들웨어의 실행 순서
compose()로 함수를 합성했을 때, f2 → f1로 뒤에 있는 함수부터 실행되는 것을 확인할 수 있음. 하지만, Redux에서 미들웨어는 f1 → f2 순서로 실행됨!? 결론부터 말하면 미들웨어가 커링 함수이기 때문에 위 예시와는 다른 순서로 실행됨.
compose()에 의해 합성되기 전에 두 미들웨어 mw1, mw2는 위와 같은 형태의 함수.
compose(mw1, mw2)로 위와 같은 함수가 반환되는데, compose의 결과에 store.dispatch를 args로 전달함
이렇게 변형된 dispatch가 각 컴포넌트에서 dispatch(action)처럼 사용되므로 doSomething1() → doSomething2() → dispatch(action) 순서로 미들웨어가 실행됨.
'JavaScript > Redux' 카테고리의 다른 글