본문 바로가기

Redux

[Redux 강의노트] Redux Middleware | logger, thunk

반응형

리덕스 미들웨어 

액션이 디스패치될때 미들웨어에서 액션의 조건에 따라 무시처리 될 수 있다.

- 주로 비동기 작업을 처리 할 때 사용된다. (API요청)

 

**배울 내용 정리**

- 리덕스 미들웨어 직접 만들기

- redux-logger 사용하기 : 액션, 상태를 출력해주는 라이브러리 사용해보기

- redux-thunk 사용하기 : 비동기 작업 처리

- redux-saga 사용하기 :  비동기 작업 처리

 

 

📌 리덕스 미들웨어 직접 만들기

 

1. 리덕스 프로젝트 생성하기 

2. 리덕스 미들웨어 직접 만들기

 

리덕스 미들웨어는 하나의 함수이다.

 

function middleware(store){
	return function (next){
    	return function (action){
        	//하고 싶은 작업..
        }
    }
}

 

velopert

 

- 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