본문으로 바로가기

Redux 미들웨어, redux-thunk

category State Management/⚛ Redux 2020. 7. 18. 23:42

redux-thunk, redux-promise에 대해서는 아래 게시판에서 간략하게 살펴본 적 있습니다.

여기서는 조금 더 자세하게 살펴봅시다.

applyMiddleware, compose를 활용하여 enhancer 만들기 사용하기

 

 

커스텀 미들웨어

직접 미들웨어를 만들 수 있습니다.

const loggerMiddleware = (store) => (next) => (action) => {
  console.log("middleware!!");
  
  // next로 넘겨주지 않으면 리듀서로 액션을 진행하지 못합니다.
  next(action);
};

export default loggerMiddleware;
import loggerMiddleware from "./lib/loggerMiddleware";

const store = createStore(rootReducer, applyMiddleware(loggerMiddleware));

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

 

 

 

redux-logger

npm i redux-logger
import { createLogger } from "redux-logger";

const logger = createLogger();
const store = createStore(rootReducer, applyMiddleware(logger));

 

 

미들웨어를 활용하여 비동기 처리하기

action을 plain한 객체가 아닌 함수 형태로 만든 후에 실행한 결과를 plain 객체로 돌려주면 됩니다.

const thunkMiddleware = (store) => (next) => (action) => {
  // action이 객체가 아닌 함수인 경우 비동기라 가정하고 처리하자.
  if (typeof action === "function") {
    // action 함수에 next, getState 함수를 넘겨서 활용할 수 있도록 하자
    return action(next, store.getState);
  }
  next(action);
};

 

action 함수는 다음과 같습니다

// async action
const asyncLogin = (data) => {
  return (next, getState) => {
    // 요청 보내기
    next(loginRequest(data));

    try {
      // 로그인 과정 2초
      setTimeout(() => {
        next(loginSuccess({ id: 1, name: "darren", admin: true }));
      }, 2000);
    } catch (error) {
      next(loginFailure(error));
    }
  };
};

const loginRequest = (data) => {
  return {
    type: LOG_IN_REQUEST,
    data,
  };
};

const loginSuccess = (data) => {
  return {
    type: LOG_IN_SUCCESS,
  };
};

const loginFailure = (error) => {
  return {
    type: LOG_IN_FAILURE,
    error,
  };
};

 

 

redux-thunk

비동기 함수 형태의 액션을 디스패치할 수 있게 됩니다. 비동기를 처리하는 미들웨어에서 가장 자주 사용된다고 합니다.

npm i redux-thunk
import ReduxThunk from "redux-thunk";

const store = createStore(rootReducer, applyMiddleware(logger, ReduxThunk));

 

비동기로 처리해야 하는 데이터 페칭 관련 함수를 redux-thunk를 이용해서 사용해보겠습니다.

왜 이런 처리를 해야 할까요? 그야 각 컴포넌트 단에서 state관리를 해주는 것보다 전역 차원에서 데이터를 페칭하고 활용하는 편이 더욱 편리하기 때문입니다.

 

우선 다음과 같이 데이터를 페칭해오는 함수가 존재한다면,

import axios from "axios";

export const getPost = (id) =>
  axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`);

export const getUsers = (id) =>
  axios.get(`https://jsonplaceholder.typicode.com/posts/users`);

 

이를 처리하는 reducer와 action들은 다음과 같이 작성할 수 있습니다.

saga에서도 마찬가지로 하나의 요청마다 3개의 액션을 생성해줘야 합니다.

 

REQUEST (요청 날림)

SUCCESS (요청 성공)

FAILURE (요청 실패)

 

로 나뉘로, 액션이 이 3개로 구성합니다.

 

import { handleActions } from "react-actions";
import * as api from "../lib/api";

// api 요청에 따른 액션 타입은 요청, 성공, 실패 3개로 나뉩니다.
const GET_POST = "sample/GET_POST";
const GET_POST_SUCCESS = "sample/GET_POST_SUCCESS";
const GET_POST_FAILURE = "sample/GET_POST_FAILURE";

// thunk 함수를 생성합니다.
export const getPost = (id) => async (dispatch) => {
  dispatch({ type: GET_POST }); // 요청을 보냈습니다.
  
  try {
    const response = await api.getPost(id);
    dispatch({ type: GET_POST_SUCCESS, payload: response.data });
  } catch (e) {
    dispatch({ type: GET_POST_FAILURE, payload: e, error: true });
    throw e;
  }
};

const initialState = {
  loading: {
    GET_POST: false,
  },
  post: null,
};

// handleActions을 이용해 reducer를 만듭니다.
const sample = handleActions(
  {
    [GET_POST]: (state) => ({
      ...state,
      loading: {
        ...state.loading,
        GET_POST: true,
      },
    }),
    [GET_POST_SUCCESS]: (state, action) => ({
      ...state,
      loading: {
        ...state.loading,
        GET_POST: false,
      },
      post: action.payload,
    }),
    [GET_POST_FAILURE]: (state, action) => ({
      ...state,
      loading: {
        ...state.loading,
        GET_POST: false,
      },
      post: action.payload,
    }),
  },
  initialState
);

export default sample;

 

이제 구체적인 컴포넌트에서 useSelect나 useDispatch를 이용하여 위 리듀서를 활용하여 데이터 페칭과 로딩을 진행하면 됩니다.


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체