리덕스 미들웨어
액션이 디스패치될때 미들웨어에서 액션의 조건에 따라 무시처리 될 수 있다.
- 주로 비동기 작업을 처리 할 때 사용된다. (API요청)
**배울 내용 정리**
- 리덕스 미들웨어 직접 만들기
- redux-logger 사용하기 : 액션, 상태를 출력해주는 라이브러리 사용해보기
- redux-thunk 사용하기 : 비동기 작업 처리
- redux-saga 사용하기 : 비동기 작업 처리
📌 리덕스 미들웨어 직접 만들기
1. 리덕스 프로젝트 생성하기
2. 리덕스 미들웨어 직접 만들기
리덕스 미들웨어는 하나의 함수이다.
function middleware(store){
return function (next){
return function (action){
//하고 싶은 작업..
}
}
}
- next는 미들웨어에서 action을 받아왔을 때 다음 미들웨어한테 전달하는 함수
-> next 호출방법 -> next(action)
store에 미들웨어 적용하기
const myLogger = (store) => (next) => (action) => {
//action이 dispatch될 때 console에 출력하겠다
console.log(action);
//action을 다음 미들웨어한테 전달, 다음 미들웨어가 없다면 리듀서에 전달
const result = next(action);
//상태 확인 / action이 리듀서에서 처리가 되고 난 다음에 다음 상태를 가져와서 콘솔 출력
console.log(`\t`, store.getState());
//container에서 dispatch 됐을때의 결과물 / const onIncrease = () => dispatch(increase());
return result;
};
export default myLogger;
//index.js
import { applyMiddleware } from "redux";
const store = createStore(rootReducer, applyMiddleware(미들웨서 함수넣기));
결과 : console
{type: "counter/INCREASE"}
{counter: 1}
{type: "counter/INCREASE"}
{counter: 2}
{type: "counter/DECREASE"}
{counter: 1}
{type: "counter/DECREASE"}
{counter: 0}
📌 redux-logger 사용 및 미들웨어와 Devtools 함께 사용
실습내용 : redux-logger로 myLogger(직접 만든 미들웨어) 대체하기
import logger from "redux-logger";
import { composeWithDevTools } from "redux-devtools-extension"; //+ devtools도 사용
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(logger))
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
결과 : console
action counter/INCREASE @ 16:25:26.163
prev state {counter: 0}
action {type: "counter/INCREASE"}
next state {counter: 1}
action counter/INCREASE @ 16:25:26.891
prev state {counter: 1}
- 미들웨어를 직접 작성하지 않고 redux-logger 라이브러리로 미들웨어 사용가능하다.
📌redux-thunk
- 액션 객체가 아닌 함수를 dispatch 할 수 있다.
실습 내용 : 1초 후에 increase를 dispatch or decrease를 dispatch하는 thunk함수 작성해보기
//modules/counter.js
//dispatch 뒤 두번째 파라미터로 getState 사용가능
export const increaseAsync = () => (dispatch) => {
setTimeout(() => {
dispatch(increase());
}, 1000);
};
export const decreaseAsync = () => (dispatch) => {
setTimeout(() => {
dispatch(decrease());
}, 1000);
};
//CounterContainer.js
const onIncrease = () => dispatch(increaseAsync());
const onDecrease = () => dispatch(decreaseAsync());
결과 : 1초뒤에 increase, decrease가 실행된다.
📌redux-thunk로 promise 다루기
//api/posts.js
//n동안 기다려주는 역할
const sleep = (n) => new Promise((resolve) => setTimeout(resolve, n));
// sleep(1000).then(() => console.log("hello"));
//{id, title, body}
const posts = [
{
id: 1,
title: "리덕스 미들웨어",
body: "리덕스 미들웨어를 직접 만들어보기두 하쥬",
},
{
id: 2,
title: "thunk",
body: "thunk로 비동기 작업 처리해보기",
},
{
id: 3,
title: "saga",
body: "saga로도 비동기 작업 처리해보기",
},
];
//비동기 함수
export const getPosts = async () => {
await sleep(500);
return posts;
};
//비동기 함수
export const getPostById = async (id) => {
await sleep(500);
return posts.find((post) => post.id === id);
};
//modules/post.js
import * as postsAPI from "../api/posts";
//각 API마다 액션 3개씩 만든다고 생각하기
const GET_POSTS = "GET_POSTS"; //특정 요청이 시작됐다는 것을 알리는
const GET_POSTS_ERROR = "GET_POSTS_ERROR"; // 실패
const GET_POSTS_SUCCESS = "GET_POSTS_SUCCESS"; //성공
const GET_POST_ID = "GET_POST_ID"; //특정 요청이 시작됐다는 것을 알리는
const GET_POST_ID_ERROR = "GET_POST_ID_ERROR"; // 실패
const GET_POST_ID_SUCCESS = "GET_POST_ID_SUCCESS"; //성공
//thunk 생성함수 만들기 - getPosts
export const getPosts = () => async (dispatch) => {
//요청시작
dispatch({ type: GET_POSTS });
//API호출
try {
const posts = await postsAPI.getPosts();
//성공시
dispatch({
type: GET_POSTS_SUCCESS,
posts,
});
} catch (e) {
//실패시
dispatch({
type: GET_POSTS_ERROR,
error: e,
});
}
};
//thunk 생성함수 만들기 - getPostId
export const getPostId = (id) => async (dispatch) => {
//요청시작
dispatch({ type: GET_POST_ID });
//API호출
try {
const post = await postsAPI.getPosts(id);
//성공시
dispatch({
type: GET_POST_ID_SUCCESS,
post,
});
} catch (e) {
//실패시
dispatch({
type: GET_POST_ID_ERROR,
error: e,
});
}
};
//초기값
const initialState = {
posts: {
loading: false,
data: null,
error: null,
},
post: {
loading: false,
data: null,
error: null,
},
};
//reducer
export default function posts(state = initialState, action) {
switch (action.type) {
case GET_POSTS:
return {
...state,
posts: {
loading: true,
data: null,
error: null,
},
};
case GET_POSTS_SUCCESS:
return {
...state,
posts: {
loading: false,
data: action.posts,
error: null,
},
};
case GET_POSTS_ERROR:
return {
...state,
posts: {
loading: false,
data: null,
error: action.error,
},
};
case GET_POST_ID:
return {
...state,
post: {
loading: true,
data: null,
error: null,
},
};
case GET_POST_ID_SUCCESS:
return {
...state,
post: {
loading: false,
data: action.post,
error: null,
},
};
case GET_POST_ID_ERROR:
return {
...state,
post: {
loading: false,
data: null,
error: action.error,
},
};
default:
return state;
}
}
↓ 유틸함수로 Refactoring하기
- 반복되는 함수 정리를 위해 유틸함수 사용하기
//lib/asyncUtills.js
//중복되는 코드 제거를 위해 utills 함수 사용하기
export const reducerUtills = {
initial: (data = null) => ({
loading: false,
data,
error: null,
}),
loading: (prevState = null) => ({
loading: true,
data: prevState,
error: null,
}),
success: (data) => ({
loading: false,
data,
error: null,
}),
error: (error) => ({
loading: false,
data: null,
error,
}),
};
export const createPromiseThunk = (type, pomiseCreator) => {
//배열 비구조화할당으로 SUCCESS가 붙은 type이 됨
const [SUCCESS, ERROR] = [`${type}_SUCCESS`, `${type}_ERROR`];
const thunkCreator = (param) => async (dispatch) => {
dispatch({ type });
try {
const payload = await pomiseCreator(param);
dispatch({
type: SUCCESS,
payload,
});
} catch (e) {
dispatch({
type: ERROR,
payload: e,
error: true,
});
}
};
return thunkCreator;
};
export const handleAsyncActions = (type, key) => {
const [SUCCESS, ERROR] = [`${type}_SUCCESS`, `${type}_ERROR`];
//reducer refactor
const reducer = (state, action) => {
switch (action.type) {
case type:
return {
...state,
[key]: reducerUtills.loading(),
};
case SUCCESS:
return {
...state,
[key]: reducerUtills.success(action.payload),
};
case ERROR:
return {
...state,
[key]: reducerUtills.error(action.payload),
};
default:
return state;
}
};
return reducer;
};
------------------------------------↓ refactor----------------------------------------
//thunk 생성함수 만들기 - getPosts, getPostId / refactor
export const getPosts = createPromiseThunk(GET_POSTS, postsAPI.getPosts);
export const getPostId = createPromiseThunk(GET_POST_ID, postsAPI.getPostById);
//초기 데이터 - refactor
const initialState = {
posts: reducerUtills.initial(),
post: reducerUtills.initial(),
};
//reducer - refactor
export const getPostsReducer = handleAsyncActions(GET_POSTS, "posts");
export const getPostReducer = handleAsyncActions(GET_POST_ID, "post");
export default function posts(state = initialState, action) {
switch (action.type) {
case GET_POSTS:
case GET_POSTS_SUCCESS:
case GET_POSTS_ERROR:
return getPostsReducer(state, action);
case GET_POST_ID:
case GET_POST_ID_SUCCESS:
case GET_POST_ID_ERROR:
return getPostReducer(state, action);
default:
return state;
}
}
'Redux' 카테고리의 다른 글
Redux Toolkit (0) | 2021.05.05 |
---|---|
[Redux 강의노트] Redux Middleware | redux-saga (0) | 2021.01.13 |
[Redux강의노트] Redux (0) | 2021.01.03 |
React → Redux | Redux 101 (2/2) (0) | 2020.11.24 |
VanillaJS → Redux | Redux 101 (1/2) (0) | 2020.11.18 |