안녕하세요. Narvis2 입니다.
이번 시간에는 Redux Middleware에 대하여 알아보도록 하겠습니다.
Redux Middleware는 REST API 요청 상태를 관리하기 위해 주로 사용합니다.
Redux Middleware를 사용하면 Action이 Dispatch된 다음, Reducer에서 해당 Action을 받아와서 Update하기 전에 추가적인 작업을 할 수 있습니다.
자세한 건 밑에서 알아보도록 하겠습니다.
🍀 Redux Middleware
Action을Dispatch했을 때Reducer에서 이를 처리하기에 앞서 사전에 지정된 작업을 실행할 수 있게 해줌 즉,Action과Reducer사이의 중간자- 특정 조건에 따라
Action이 무시되게 만들 수 있음 Action을console에 출력하거나, 서버쪽에logging을 할 수 있음Action이Dispatch되었을 때 이를 수정해서Reducer에게 전달되도록 할 수 있음- 특정
Action이 발생했을 때 이에 기반하여 다른Action이 발생되도록 할 수 있음 - 특정
Action이 발생했을 때 특정Javascript함수를 실행시킬 수 있음 - ✅
Redux Middleware는 비동기 작업을 처리할 때 주로 사용- 비동기 작업에 관련된
Middleware라이브러리redux-thunk- 함수를 기반으로 작동
redux-toolkit에 내장되어 있어 적용하기가 간단redux에서type을 지닌 객체가 아닌, 함수 타입을Dispatch할 수 있게 해줌
redux-sagaGenerator를 기반으로 사용- 특정 경우에 특정
Action을 모니터링 할 수도 있음 - 특정
Action이Dispatch되었을때 원하는 함수를 호출하거나, 또는라우터를 통해 다른 주소로 이동하는 것이 가능
redux-observableRxJs를 기반으로 작동- 특정 경우에 특정
Action을 모니터링 할 수도 있음 - 특정
Action이Dispatch되었을때 원하는 함수를 호출하거나, 또는라우터를 통해 다른 주소로 이동하는 것이 가능
redux-promise-middleware
- 비동기 작업에 관련된
☘️ Redux-Thunk
redux에서 비동기 작업을 처리 할 때 가장 많이 사용하는middleware- 함수를 기반으로 작동
action객체가 아닌 함수를Dispatch할 수 있게 해줌.Redux DevTools와 함께 사용적용 👉
yarn add redux-thunk1️⃣ 예제 )
redux-thunkmiddleware적용 >App.tsx👇1 2 3 4 5
import rootReducer from "./src/modules"; import { applyMiddleware, createStore } from "redux"; import thunk from "redux-thunk"; const store = createStore(rootReducer, applyMiddleware(thunk));
2️⃣ 예제 )
thunk함수 생성 >Redux Module에 작성 👇1 2 3 4 5 6 7
// ✅ thunk 함수 생성 export const increaseAsync = () => (dispatch: Dispatch<CounterAction>) => { setTimeout(() => dispatch(increase()), 1000); }; export const decreaseAsync = () => (dispatch: Dispatch<CounterAction>) => { setTimeout(() => dispatch(decrease()), 1000); };
설명 👉
CounterAction을AnyAction으로 교체해도 상관 없음3️⃣ 사용 )
thunk함수 사용 👇1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/** * ✅ 현재 상태를 조회 * 상태를 조회할 때는 state의 타입을 RootState로 지정해야함 */ const counter = useSelector((state: RootState) => state.counter); // Dispatch 함수를 가져옴 (action을 발생시켜 상태를 업데이트) const dispatch = useDispatch(); function onIncrease() { dispatch(increaseAsync()); } function onDecrease() { dispatch(decreaseAsync()); }
☘️ Redux-Thunk + Promise
⚠️
Promise를 다루는Redux Module을 다룰 때 주의 사항- 1️⃣
Promise가 시작(start), 성공(success), 실패(failure)했을 때 다른Action을Dispatch해야함 - 2️⃣ 각
Promise마다thunk함수를 만들어주어야 함 - 3️⃣
Reducer에서Action에 따라 로딩중(loading), 결과(result), 에러(error) 상태를 변경해주어야 함
1️⃣ 예제 )
modules/posts.tsx>Redux Module작성 👇1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
import { AnyAction, Dispatch } from "redux"; import * as postsAPI from "../api/posts"; // api/posts 안의 함수 모두 불러오기 // ✅ 밑의 대문자 상수는 모두 Action Type임 // 포스트 여러개 조회하기 const GET_POSTS = "GET_POSTS"; // 요청이 시작됨 const GET_POSTS_SUCCESS = "GET_POSTS_SUCCESS"; // 성공 const GET_POSTS_ERROR = "GET_POSTS_ERROR"; // 실패 // 포스트 하나 조회하기 const GET_POST = "GET_POST"; // 요청이 시작됨 const GET_POST_SUCCESS = "GET_POST_SUCCESS"; // 성공 const GET_POST_ERROR = "GET_POST_ERROR"; // 실패 /** * thunk 를 사용할 때, 꼭 모든 Action들에 대하여 Action 생성함수를 만들 필요는 없음 * 그냥, thunk 함수에서 바로 action 객체를 만들어 주어도 괜찮음 */ export const getPosts = () => async (dispatch: Dispatch<AnyAction>) => { dispatch({ type: GET_POSTS }); try { const posts = await postsAPI.getPosts(); dispatch({ type: GET_POSTS_SUCCESS, posts }); } catch (error) { dispatch({ type: GET_POSTS_ERROR, error: error }); } }; // ✅ thunk 함수에서도 파라미터를 받아와서 사용 가능 export const getPost = (id: number) => async (dispatch: Dispatch<AnyAction>) => { dispatch({ type: GET_POST }); try { const post = await postsAPI.getPostById(id); dispatch({ type: GET_POST_SUCCESS, post }); } catch (error) { dispatch({ type: GET_POST_ERROR, error: error }); } }; // ✅ 초깃값 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: return { ...state, post: { loading: true, data: null, error: null, }, }; case GET_POST_SUCCESS: return { ...state, post: { loading: true, data: action.post, error: null, }, }; case GET_POST_ERROR: return { ...state, post: { loading: true, data: null, error: action.error, }, }; default: return state; } }
2️⃣ 예제 )
thunk사용 👇1 2 3 4 5 6 7
const { data, loading, error } = useSelector((state) => state.posts.posts); const dispatch = useDispatch(); // 컴포넌트 마운트 후 포스트 목록 요청 useEffect(() => { dispatch(getPosts()); }, [dispatch]);
- 1️⃣