const middleware = store => next => action => {
// 하고 싶은 작업...
}
미들웨어는 함수를 두 번 연달아 리턴하는 함수다. 함수형 프로그래밍 느낌?
출처 : https://react.vlpt.us/redux-middleware/02-make-middleware.html
이런 식으로 작동한다. 스토어에 여러 개 미들웨어가 등록될 수 있고, 새로운 액션이 디스패치 되면 첫 번째 등록한 미들웨어가 호출된다. 미들웨어에서 next(액션)을 호출하게 되면 다음 미들웨어로 액션이 넘어간다. 만약 미들웨어에서 store.dispatch를 사용하면 다른 액션을 추가적으로 사용 가능하다.
root 파일에 이렇게 적용해준다.
console.log('\t', store.getState());
이렇게 조회가 가능하다.
이렇게 logger를 이용해서 여러 개의 미들웨어를 등록할 수 있다!
이뻐...
redux-thunk는 비동기 작업을 할 때 가장 많이 쓰이는 미들웨어다. 액션 객체가 아닌 함수를 디스패치 할 수 있다!
함수를 디스패치 할 때에는, 해당 함수에서 dispatch 와 getState 를 파라미터로 받아와주어야 합니다. 이 함수를 만들어주는 함수를 우리는 thunk 라고 부릅니다.
const getComments = () => (dispatch, getState) => {
// 이 안에서는 액션을 dispatch 할 수도 있고
// getState를 사용하여 현재 상태도 조회 할 수 있습니다.
const id = getState().post.activeId;
// 요청이 시작했음을 알리는 액션
dispatch({ type: 'GET_COMMENTS' });
// 댓글을 조회하는 프로미스를 반환하는 getComments 가 있다고 가정해봅시다.
api
.getComments(id) // 요청을 하고
.then(comments => dispatch({ type: 'GET_COMMENTS_SUCCESS', id, comments })) // 성공시
.catch(e => dispatch({ type: 'GET_COMMENTS_ERROR', error: e })); // 실패시
};
async/await도 가능하다!!
root 파일에 thunk 적용한 모습!
logger를 사용할 경우, 인자의 가장 마지막에 와야한다.
counter.js
CounterContainer.js
counter.js에서 thunk를 작성한다. getState를 쓰지 않는다면 첫 번째 인자에 파라미터를 받아올 필요는 없다. dispatch를 받아서 settimeout으로 1초 뒤에 increase() 액션을 실행한다. 엄청 복잡해 보이는데 커링 함수 같은 느낌??
내가 이해한 느낌은 무언가 액션이 일어나면 액션을 킵해두고 thunk 내부에서 정의한 것에 의해 다시 실행되는 것!
요렇게 api를 만들어둔다.
이제 posts 모듈을 만든다!
1. 프로미스가 시작, 성공, 실패했을때 다른 액션을 디스패치해야합니다.
2. 각 프로미스마다 thunk 함수를 만들어주어야 합니다.
3. 리듀서에서 액션에 따라 로딩중, 결과, 에러 상태를 변경해주어야 합니다.
한 개 조회했을 때, 여러 개 조회했을 때 각 성공, 에러에 대한 타입을 다 만들어준다.
thunk도 만들어주고 try/catch로 시작, 성공, 실패의 경우를 나눠준다.
reducer 파트도 만들어주면 끝!
반복되는 코드를 줄여보자! src/lib/asyncUtils.js
를 만든다! lib 폴더를 써보는 건 처음이야!!!
asyncUtils.js
promise를 이용해서 thunk를 만드는 createPromiseThunk
와 reducer에 필요한 함수를 정의한 reducerUtils
가 있다. 추상화 단계가 한 단계 높아졌는데 결국 리팩토링은 반복 함수 제거 -> 추상화인 거 같다!
createPromiseThunk
type
은 GET_POST, GET_POSTS 두 개니까 비구조화로 SUCCESS, ERROR를 가져온다. 신기..
param
을 리턴하고 async로 dispatch 가져와서 try/catch를 시작하는데 클로저 패턴이다. 클로저 쓰이는 거 처음 본다..
reducerUtils
말로 설명하기 힘든데 객체 안에 함수를 만들어서 맵핑했다!
createPromiseThunk
는 type, promiseCreator 함수를 인자로 받아서 익명 함수를 리턴하는데 param이란 인자를 받아서 async로 dispatch 받고 try/catch 실행한다.
즉, createPromiseThunk(GET_POST, getPosts)
이런 식으로 넣어주면 될 것 같다!
getPosts는 결국 postApi 안에 있는 함수를 가져오는 것이니까 postsAPI.getPosts
가 들어가야 한다.
export를 해줘야 한다!!
initialState도 reducerUtils를 이용해서 바꿔준다.
하.. 진짜 어렵다...
아직도 reducer는 반복이 많다. 끝까지 가는 거다...
함수를 하나 더 만들어서 반복되는 요소를 단순화 시켜준다.
진짜 굉장하다..handleAsyncActions
은 커링이다!!!
이제 플로우을 역추적으로 가보자. 화면이 첫 렌더되면
PostListContainer.js
useEffect로 dispatch(getPosts())
를 실행한다. 그러면 posts module에 있는 getPosts가 실행된다.
createPromiseThunk로 넘어가고(이것이 미들웨어 가로채기?) 요청 시작를 하는 dispatch가 실행된다.(추측)
그러면 posts 모듈에 reducer가 실행되고 type에 따라 handleAsyncActions를 실행한다. 그러면 logger가 반응하면서 액션을 전달한다(어려움)
이제 다시 thunk로 넘어와서 try 파트가 실행된다.
posts Api에서 data가 넘어오고 payload에 담긴다. dispatch로 paylod를 module reducer로 보낸다.
그리고 컴포넌트에 useSelector를 통해 렌더. 진짜 기나긴 여정이다...
이제 조회 기능을 만들어보자
루트 파일에 import { BrowserRouter } from 'react-router-dom';
추가
id값을 받아서 dispatch하는 container 생성
PostListPage
와 PostPage
각각 생성PostPage
는 match.params로 id를 가져와서 container에 뿌려준다.
완성했지만 로딩이 너무 많다. api 로딩을 개선하자!!!
출처 : https://react.vlpt.us/redux-middleware/02-make-middleware.html