액션과 리듀서 중간에서 추가적인 작업이나 전처리등을 수행하는 부분.
(proxy 비슷한 느낌)
액션 -> 리듀서
액션 -> 미들웨어 -> 리듀서
미들웨어에서 할 수 있는 일은
등등이 있다.
더 많은 정보
출처 https://react.vlpt.us/redux-middleware/
미들웨어를 직접 구현하는 법까지 가르쳐준다.
추가하고 싶은 미들웨어들을 징검다리처럼 이어주는 함수.
미들웨어가 도대체 어떤 원리로 동작하는지 찾아봤다.
핵심은 이 applyMiddleware 함수인데 정말 감각적인 구현이다.
함수 합성을 재귀적인 사고 방식으로 구현했다.
어떻게 동작하는지 차근차근 알아보자.
// 미들웨어들을 이어주는 함수
const applyMiddleware = (...middlewares) => {
// 인자로 넘어온 미들웨어 함수들을 새로 복사한 다음 거꾸로 뒤집기
middlewares = [...middlewares].reverse();
// 미들웨어들을 연결
const dispatch = middlewares.reduce((dispatch, middleware) => (
dispatch = middleware(store)(dispatch)
), store.dispatch);
// 스토어의 복사본 리턴
return Object.assign({}, store, { dispatch });
}
// 로깅 미들웨어
const logger = store => next => action => {
console.log('dispatching', action);
const result = next(action);
console.log('next state', store.getState());
return result;
};
// 충돌 리포팅 미들웨어
const crashReporter = store => next => action => {
try {
return next(action);
}
catch (err) {
console.error('Caught an exception!', err);
throw err;
}
};
// 미들웨어들을 연결
applyMiddleware(logger, crashReporter);
코드를 차근차근 해석해보자.
// 인자로 넘어온 미들웨어 함수들을 새로 복사한 다음 거꾸로 뒤집기
middlewares = [...middlewares].reverse();
// 미들웨어들을 연결
const dispatch = middlewares.reduce((dispatch, middleware) => (
dispatch = middleware(store)(dispatch)
), store.dispatch);
예제에 나온 코드로 알아보자.
2 - 1.
이 예제에서는 첫 번째 미들웨어로 crashReporter함수가 순회된다. (reverse 했기 때문)
crashReporter(store)(dispatch)를 하게 되면
action => {
try {
return next(action);
}
catch (err) {
console.error('Caught an exception!', err);
throw err;
}
};
내부적으로 action 객체를 받아서 reducer에게 dispatch를 하고
그 과정에서 에러가 났을 땐 에러를 보고하는 함수가 리턴된다.
2 - 2.
리턴된 dispatch를 다음 미들웨어인 logger에게 넘겨주면
action => {
console.log('dispatching', action);
const result = next(action); // crashReporter에게 전달
console.log('next state', store.getState()); // 상위의 상위 스코프에 있는 store 인스턴스
return result;
};
action을 로깅하고 crashReporter 미들웨어에게 전달한 다음
crashReporter 미들웨어가 본인의 일처리를 끝나고 reducer에게
action 전달까지 완료돼서 상태가 바뀌면
다음 상태를 로깅하는 함수가 리턴된다.
2 - 3.
// 스토어의 복사본 리턴
return Object.assign({}, store, { dispatch });
리턴된 dispatch 함수를 스토어의 새로운 dispatch로 덮어씌운다.
그러면 action을 dispatch했을 때
action -> logger -> crashReporter -> reducer의 흐름으로 이어지게 된다.
알면 정말 별 거 없다.
그저 직렬적으로 연결된 미들웨어들이
중간에서 자기 할 일을 한 뒤에 다음 턴으로 넘기는 방식이다.
마치 함수형 프로그래밍에서 파이프라인을 이어주는 느낌과 비슷하다.
함수형의 파이프라인은 나열된 함수들을 순서대로 실행시켜주는 함수라면
applyMiddleware는 나열된 함수들이 내부적으로 다음 함수를 실행시키도록 합성하는 함수다.
조금 더 직관적인 구현
// 미들웨어들을 이어주는 함수
const applyMiddleware = (...middlewares) => {
// 미들웨어들을 연결
const dispatch = middlewares.reduceRight((dispatch, middleware) => (
dispatch = middleware(store)(dispatch)
), store.dispatch);
// 스토어의 복사본 리턴
return Object.assign({}, store, { dispatch });
}
사람마다 다르겠지만 개인적으로 이 버전이 좀 더 이해하기 쉽다.
이전 버전과 다른 점은 reverse + reduce -> reduceRight로 바꿔주었다.
예를 들어 미들웨어가 A, B, C가 있을 때를 생각해보자.
// 정확히는 이런 함수
action => {
do something logic of C // 미들웨어 C의 로직을 타고
store.dispatch(action); // reducer에게 넘김
// 추가 로직...
};
// 정확히는 이런 함수
action => {
do something logic of B // 미들웨어 B의 로직을 타고
passActionToC(action); // 미들웨어 C에게 넘김 (passActionToC는 next입니다.)
// 추가 로직...
};
// 정확히는 이런 함수
action => {
do something logic of A // 미들웨어 A의 로직을 타고
passActionToB(action); // 미들웨어 B에게 넘김 (passActionToB는 next입니다.)
// 추가 로직...
};
끝에서부터 차근차근 이어주는 느낌이라 더 직관적이지 않은가?
아마 실제 구현은 reduceRight를 사용하지 않았을까 싶다.
더 자세한 동작 원리
출처 https://lunit.gitbook.io/redux-in-korean/advanced/middleware