redux Middleware는 리덕스 사이트의 advanced 항목에서 확인해 볼 수 있습니다.
미들웨어는 store를 설정하는 부분에서 설정해주어야 합니다.
const store = createStore(reducer, {이 부분});
createStore의 두번째 인자로 인헨서라는 것을 추가해 줄 수 있는데 이 것이 미들웨어입니다.
미들웨어는 function입니다. 인자로 store를 받을 수 있습니다.(아래)
import { applyMiddleware, createStore } from "redux";
function middleware1(store) {
console.log("middleware1", 0);
return (next) => {
console.log("middleware1", 1);
return action => {
console.log("middleware1", 2);
const returnValue = next(action);
console.log("middleware1", 3);
return returnValue;
}
}
}
const store = createStore(reducer, applyMiddleware(middleware1));
리턴은 함수입니다. next
라고 인자로 들어오는데, 이 next는 현재의 미들웨어가 아니라 다음의 미들웨어를 지칭하는 합니다. 다음 미들웨어가 없으면 store로 이동하여 dispatch합니다.
아래는 실행결과
새로 고침으로 첫 mount 때에만 console로 0, 1, 2, 3
이 출력되고, 이후에 List에 item을 추가할 때에는 2, 3
만 출력됩니다.
미들웨어를 하나 더 추가하고(middleware2
) middleware1
다음순서로 등록하겠습니다. (아래)
import { applyMiddleware, createStore } from "redux";
function middleware1(store) {
console.log("middleware1", 0);
return (next) => {
console.log("middleware1", 1);
return action => {
console.log("middleware1", 2);
const returnValue = next(action);
console.log("middleware1", 3);
return returnValue;
}
}
}
function middleware2(store) {
console.log("middleware2", 0);
return (next) => {
console.log("middleware2", 1);
return (action) => {
console.log("middleware2", 2);
const returnValue = next(action);
console.log("middleware2", 3);
return returnValue;
};
};
}
const store = createStore(reducer, applyMiddleware(middleware1, middleware2));
아래는 실행결과
store를 최초의 인자로 받았기 때문에 store에 getState()
를 할 수 있고, store에 dispatch()
를 할 수 있습니다.
그래서 state를 얻어서 다른 처리를 하거나, 아니면 dispatch를 추가로 더 보내는 등의 작업을 할 수 있습니다.
보통 실무에서 직접 middleware를 만들어서 사용하는 경우는 없고, redux의 부가기능을 추가하기 위해서 여러가지 redux 라이브러리
들이 준비되어 있습니다. 우리는 이런 라이브러리들을 활용하여 redux store를 좀 더 고급기능으로 사용할 수 있게 됩니다.
다음시간에는 store에 미들웨어를 설치해서 어떤 도움을 받을 수 있는지 알아보겠습니다.
이번에는 redux에 redux-devtools를 적용해 보겠습니다.
redux-devtools도 미들웨어를 설치해서 우리들의 브라우저에 있는 devtools에 연결하는 작업이 필요합니다.
npm i redux-devtools-extension -D
import { composeWithDevTools } from "redux-devtools-extension"
import해줍니다.import { applyMiddleware, createStore } from "redux";
import reducer from "./reducers/reducer";
import { composeWithDevTools } from "redux-devtools-extension";
const store = createStore(reducer, composeWithDevTools(applyMiddleware()));
export default store;
여기까지 redux-devtools-extension
으로 데이터를 보낼 준비가 되었습니다.
redux-devtools-extension
의 github주소
https://github.com/zalmoxisus/redux-devtools-extension
여기서 각각 크롬, 파이어폭스, 일렉트론 등에서 설치하는 방법이 나와있습니다.
크롬 웹 스토어에서 확장프로그램으로 설치할 수 있습니다.
설치 후 크롬 개발자도구에서 확인해 보겠습니다.
우리 애플리케이션에서 일어나고 있는 모든 스토어의 변화에 대해서 왼쪽 창에 등록이 되어 있습니다.
오른쪽 창에서는 왼쪽에서 선택한 액션에 대한 정보를 확인할 수 있습니다.
우리가 직접 여기서 액션을 dispatch를 해볼수도 있습니다.
아래창에서 recoding과 debuging 기능도 사용해 볼 수 있습니다.
redux에 문제가 있다고 판단될 때 이 redux-devtools-extension
를 이용하여 디버깅을 합니다.
다음 시간에는 다음 middleware를 사용해보겠습니다.
redux의 미들웨어 중에서 가장 많이 쓰이는 라이브러리인 redux-thunk에 대해서 배워보겠습니다.
thunk: (떵크 or 청크)
https://github.com/reduxjs/redux-thunk
우리의 프로젝트에 redux-thunk를 설치하고, 우리의 비동기 로직을 redux-thunk로 변경해보도록 하겠습니다.
npm i redux-thunk
import { applyMiddleware, createStore } from "redux";
import reducer from "./reducers/reducer";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)));
export default store;
import thunk from "redux-thunk"
import 합니다.이대로 실행해도 아무 변화도 없습니다. 왜냐하면
thunk
함수는 액션 생성자
가 함수를 리턴할 때에만
반응합니다. 액션 객체를 return 할때에는 기존 동작처럼 동작합니다.
여기까지 middleware 설정은 끝났습니다.
앞서 우리가 만든 컴포넌트중 비동기로 데이터를 가져와서 List를 출력하는 컴포넌트가 있었고, 이 컴포넌트의 뷰를 처리하는 로직과 비동기로 데이터를 받아오는 로직(HOC)으로 파일 단위로 분리하였습니다.(UserList.jsx, UserListContainer.jsx)
비동기로직이 있는 컴포넌트로 가서..
// UserListContainer.jsx
import UserList from "../components/UserList";
import { useSelector, useDispatch } from "react-redux";
import { useCallback } from "react";
import { getUsersStart, getUsersSuccess, getUsersFail } from "../redux/actions";
import axios from "axios";
export default function UserListContainer() {
console.log("UserListContainer start");
const users = useSelector((state) => state.users.data);
const dispatch = useDispatch();
const getUsers = useCallback(async () => {
try {
dispatch(getUsersStart());
const res = await axios.get("https://api.github.com/users");
dispatch(getUsersSuccess(res.data));
} catch (error) {
dispatch(getUsersFail(error));
}
}, [dispatch]);
return <UserList users={users} getUsers={getUsers} />;
}
getUsers 안에서 비동기 로직을 처리하고 있습니다.
getUsers 함수를 액션 생성 함수로 바꾸고, 함수를 리턴하는 방식으로 수정해보겠습니다.
먼저 action.js로 가서 (기존처럼 액션 객체
가 아닌) 함수를 리턴
하는방식으로 액션 생성 함수를 추가하겠습니다.
// action.js
export function getUsersThunk() {
return async (dispatch) => {
try {
dispatch(getUsersStart());
const res = await axios.get("https://api.github.com/users");
dispatch(getUsersSuccess(res.data));
} catch (error) {
dispatch(getUsersFail(error));
}
};
}
함수를 리턴할 때 그냥 함수가 아닌 함수의 인자로 dispatch
를 넘겨줍니다.
우리는 dispatch
를 보통 컨테이너쪽
에서 처리하고 있었습니다. 하지만 이제는 액션을 생성하는 쪽
에서 할 수 있도록 dispatch를 여기로 가지고 들어온 것입니다.
이제 다시 비동기 로직이 있었던 컨테이너쪽에서는 위에서 만든 비동기 함수를 리턴하고 있는 함수를 dispatch로 보내는 작업만 해주면됩니다.
// UserListContainer.jsx
import UserList from "../components/UserList";
import { useSelector, useDispatch } from "react-redux";
import { useCallback } from "react";
import { getUsersThunk } from "../redux/actions";
export default function UserListContainer() {
console.log("UserListContainer start");
const users = useSelector((state) => state.users.data);
const dispatch = useDispatch();
// 기존 비동기 코드
// const getUsers = useCallback(async () => {
// try {
// dispatch(getUsersStart());
// const res = await axios.get("https://api.github.com/users");
// dispatch(getUsersSuccess(res.data));
// } catch (error) {
// dispatch(getUsersFail(error));
// }
// }, [dispatch]);
// thunk를 반응하게 만들 코드
const getUsers = useCallback(() => {
dispatch(getUsersThunk());
}, [dispatch]);
return <UserList users={users} getUsers={getUsers} />;
}
이렇게 하고 실행하면 큰 문제없이 잘 됩니다.
기존에는 비동기로직이 컨테이너에 있었는데, 이제는 액션을 다루는
액션생성함수에서 처리할 수 있게 됩니다. 그리고 컨테이너는 그냥 액션생성자를 컴포넌트에 전달하는 역할만하게 됩니다.
이렇게하면 실제로 dispatch되는 로직들은 전부 다 액션에서 관리가 되기 때문에 관심사가 적절히 분리됩니다.
그럼 thunk은 어떻게 이런 일을 할 수 있을까요?
github의 thunk 코드를 보면 매우 간단하게 작성되어 있습니다.
간단한 코드이지만 이 코드가 우리의 비동기 로직을 수행하는 것을 편하게 도와줍니다.
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
}
}
const thunk = createThunkMiddleweare();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
많이 쓰이고, 이 라이브러리를 통해서 redux의 비동기 작업이 많이 되고 있습니다.
이번에는 비동기 처리를 위한 또 다른 미들웨어인 redux-promise-middleware
를 사용해보겠습니다.