Redux
20210618 Redux04 : MiddleWare(미들웨어), 미들웨어 라이브러리(Redux-devtools, Redux-thunk, Redux-promise-middleware)
Redux 04
Redux MiddleWare (리덕스 미들웨어)
- 미들웨어가 dispatch 의 앞뒤에 코드를 추가할 수 있게 해줌
- dispatch 메소드를 통해 store로 가고 있는 Action을 가로채는 코드
- 미들웨어가 여러개면 미들웨어가 순차적으로 실행됨
- 스토어를 만들때, 미들웨어를 설정함
applyMiddleware()
함수로 미들웨어들을 묶어서createStore()
함수의 두번째 인자로 넣어줌
- 디스패치가 호출 될때 실제로 미들웨어를 통과하는 부분
- 디스패치가 호출 될때 마다 미들웨어에서 action을 인자로 받는 부분이 실행됨
- 스토어를 만들때, 미들웨어를 설정함
next()
:- next()가 호출된 곳의 미들웨어 다음에 다른 미들웨어가 있는 경우 -> 다음 미들웨어의 action을 받는 함수를 가르킴
- next()가 호출된 곳의 미들웨어 다음에 다른 미들웨어가 없는 경우 -> dispatch method
- 보통 로깅 및 충돌 보고서를 받고자 하는 경우 사용함
- 실무에서는 미들웨어를 만들어서 사용하지 않고 리덕스 미들웨어 라이브러리를 활용해서 redux의 고급기능을 사용함
- middleware를 통해서 dispatch가 되기전에 다른 state를 가져오거나, 다른 dispatch를 보내는 등의 행위를 부가적으로 할 수 있음
import { applyMiddleware, createStore } from "redux";
import reducer from "./reducers/reducer";
function middleware1(store) {
// store를 받는 것은 dispatch, getState 를 사용하려고
console.log("middleware1", 0); // (1)
return (next) => {
console.log("middleware1", 1, next); // (4)
return (action) => {
console.log("middleware1", 2); // (5)
const returnValue = next(action); // (6) - 다음 미들웨어 action을 받는 함수로 이동함
// (11) dispatch를 받고, returnValue에 할당
console.log("middleware1", 3); // (12)
return returnValue; // (13) - reducer로 보냄
};
};
}
function middleware2(store) {
console.log("middleware2", 0); // (2)
return (next) => {
console.log("middleware2", 1, next); // (3)
return (action) => {
console.log("middleware2", 2); // (7)
const returnValue = next(action); // (8) - dispatch에 action을 담아 넣음
console.log("middleware2", 3); // (9)
return returnValue; // (10)
};
};
}
const store = createStore(reducer, applyMiddleware(middleware1, middleware2));
export default store;
Redux-Devtools
npm i redux-devtools-extension -D
applyMiddleware()
를composeWithDevTools()
안에 넣어주어 연결하면, 브라우저에서 redux-devtools를 사용할 수 있음- Chrom의 경우 redux devtools 익스텐션을 설치해서 사용가능 함
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;
- 각 타입 이름에 따른 reduer, State, Action, Diff 등을 볼수 있고, 그 외의 다양한 기능들을 제공함
Redux-thunk
- 많이 사용하는 리덕스 미들웨어 라이브러리 중 하나
- 리덕스 만든 사람이 만들었음
- 리덕스에서 비동기 처리를 위한 라이브러리
- 액션 생성자를 활용하여 비동기 처리
- 액션 생성자가 액션을 리턴하지 않고, 함수를 리턴
npm i redux-thunk
- thunk는 액션 생성자가 함수를 return 할때만 반응하고,
- 액션 생성자가 원래대로 action 객체 return 하면 기존 동작 처럼 동작함
store, middleware 부분에 thunk 연결
applyMiddleware()
안에thunk
를 넣음 (thunk도 미들웨어임)- ActionCreator이 Action이 아닌 함수를 return 하므로, dispatch 하기 전에 미들웨어인 thunk에서 이를 먼저 받아서 그냥 함수를 실행 해버림
import { applyMiddleware, createStore } from "redux";
import reducer from "./reducers/reducer";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
// thunk 연결
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)));
export default store;
비동기 작업 함수를 return 하는 ActionCreator 만들기
- 액션을 생성하는 쪽에서 비동기 작업을 하고 다시 dispatch 할 수 있는 함수를 return 하게하는 ActionCreator를 만듦
- getUsersThunk라는 ActionCreator를 만들어서 사용하는데, Thunk가 있으니까 Action 객체가 아니어도 반응하게 됨
- 그리고, ActionCreator를 생성하는 곳에서 비동기 작업 함수를 만들기 때문에 더 편하게, 다른 ActionCreator를 활용해 dispatch 할수 있게 구현할수 있음
// Actions.js
// Thunk로 구현한 비동기 작업 함수
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));
}
};
}
Container에서 비동기 작업 함수 요청하기
import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import UserList from "../components/UserList";
import { getUsersThunk } from "../redux/actions";
export default function UserListContainer() {
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} />;
}
Redux-promise-middleware
- 또 다른 리덕스 비동기 미들웨어
npm i redux-promise-middleware
- store에
applyMiddleware()
안에promise
를 넣어 연결함
프로미스 미들웨어 Store에 연결
// store.js
import { applyMiddleware, createStore } from "redux";
import reducer from "./reducers/reducer";
import { composeWithDevTools } from "redux-devtools-extension";
import promise from "redux-promise-middleware";
const store = createStore(
reducer,
composeWithDevTools(applyMiddleware(promise))
);
export default store;
비동기 작업 함수를 가진 ActionCreator 만들기
- 프로미스 미들웨어의 특징은, 비동기 작업 함수를 ActionCreator에서 Action 객체 형태로 return 하는 구조로 만듦
{type: "", payload: async() => {비동기 작업}}
형식
// redux-promise-middleware Types&ActionCreator
const GET_USERS = "GET_USERS";
// promise가 자동으로 만들어 주는 Action의 Type이름
export const GET_USERS_PENDING = "GET_USERS_PENDING";
export const GET_USERS_FULFILLED = "GET_USERS_FULFILLED";
export const GET_USERS_REJECTED = "GET_USERS_REJECTED";
export function getUsersPromise() {
return {
type: GET_USERS,
payload: async () => {
const res = await axios.get("https://api.github.com/users");
return res.data;
},
};
}
Container에서 요청 함수 만들기
- 비동기 작업 Action을 dispatch 하는 함수 만들고 Component에 연결
- 해당 ActionCreator로 dispatch 하면 payload에 있는 함수를 실행하고 해당 함수에 의해 store에 반영할 데이터를 return 함
- promise 에서 비동기 작업 함수 Action을 dispatch 하면 작업을 수행하면서, 이 Action을 참고하여 type을 변조한 시작, 실패, 성공 Action을 만들어 dispatch 함
- 요청 작업 type명에
_PENDING
,_FULFILLED
,_REJECT
붙여 type을 만들어 해당 Action까지 만들어서 보내줌 - 그래서 굳이 해당 시작, 성공, 실패에 대한 ActionCreator를 만들 필요를 없게 만들어 줌
- 요청 작업 type명에
import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import UserList from "../components/UserList";
import { getUsersPromise } from "../redux/actions";
export default function UserListContainer() {
const users = useSelector((state) => state.users.data);
const dispatch = useDispatch();
const getUsers = useCallback(() => {
dispatch(getUsersPromise());
}, [dispatch]);
// GET_USERS를 dispatch 했지만, devtools에서는 GET_USERS_PENDING, GET_USERS_FULFILLED 로 되어 있음
// promise 에서 비동기 작업 합수 Action을 dispatch 하면 작업을 수행하면서, 이 Action을 참고하여 type을 변조한 시작, 실패, 성공 Action을 만들어 dispatch 함
return <UserList users={users} getUsers={getUsers} />;
}
Dispatch한 Action을 처리할 Reducer 만들기
- redux-promise-middleware가 만든 Action을 처리할 Reducer를 만들어서 store에 어떻게 저장 할건지 정해야함
- 데이터를 받아오는 Action의 Reducer라면, 해당 데이터는
action.payload
를 통해서 가져와 store에 반영함
- 데이터를 받아오는 Action의 Reducer라면, 해당 데이터는
import {
GET_USERS_FULFILLED,
GET_USERS_PENDING,
GET_USERS_REJECTED,
} from "../actions";
const initialState = {
loading: false,
data: [],
error: null,
};
export default function users(state = initialState, action) {
// redux-promise-middleware Reducer
if (action.type === GET_USERS_PENDING) {
// 시작 변경이라서 딱히 데이터를 받아오지 않아서 값만 바꾸어 주면됨
return {
...state,
loading: true,
error: null,
};
}
if (action.type === GET_USERS_FULFILLED) {
// action의 payload로 데이터를 가져옴 (payload에 들어 있던 함수의 return)
return {
...state,
loading: false,
data: action.payload,
};
}
if (action.type === GET_USERS_REJECTED) {
// error 또한, paylaod로 들어옴
return {
...state,
loading: false,
error: action.payload,
};
}
return state;
}