TIL84. 리덕스 미들웨어

조연정·2021년 6월 29일
0
post-thumbnail

'리덕스 미들웨어'를 사용해서 비동기 작업을 처리해보자.

리덕스 미들웨어란?

미들웨어는 액션과 리듀서 사이의 중간자로, 액션을 디스패치했을 때 리듀서에서 이를 처리하기에 앞서 사전에 지정된 작업들을 실행할 수 있게 해준다.
리덕스는 액션 객체가 생성되고, 디스패치를 통해 스토어에 상태 변화를 알리면 리듀서는 정해진 로직에 따른 액션을 처리한 후 새로운 상태값을 반환하는 동기적인 흐름을 통해 이뤄진다.

하지만 외부 데이터를 요청하는 비동기적인 요소들을 처리해야 할 경우가 있을텐데 이때 사용하는 것이 '리덕스 미들웨어'이다.

미들웨어는 스토어를 생성할 때 리덕스 모듈 안에 있는 applyMiddleware를 사용하여 설정해준다.

미들웨어 기본구조

화살표 함수를 연달아서 사용하는 형태로, 함수를 반환하는 함수를 반환하는 함수이다.

const loggerMiddleware = (store) => (next) => (action) => {};

export default loggerMiddleware;

//일반 function 구조
function loggerMiddleware(store) {
  return function (next) {
    return function (action) {
        console.log("기본구조")
    };
  };
};

디스패치하기 전 로직이 필요할 때 dispatch()사용
초기값
액션 로깅 {type: 'ADD_POSt'...}
액션끝
const firstMiddleware = (store) => (dispatch) => (action) => {
console.log("액션 로깅", action);
// 디스패하기 전 - 기능 추가
dispatch(action);
// 디스패치 후 - 기능추가
console.log("액션 끝");
};
const enhancer = applyMiddleware(firstMiddleware);
//enhancer 추가
const store = createStore(Reducer, initialState, enhancer);

redux-thunk

redux-thunk는 리덕스 미들웨어에서 비동기 작업을 처리하는데 사용하는 가장 대표적인 리덕스 미들웨어이다.
기존의 리덕스에서 액션생성함수에서 액션을 객체 형태로 반환해해준다. 하지만 redux-thunk에서는 액션생성함수는 객체가 아닌 함수를 반환해준다.
쉅게말해, 동기와 비동기를 구분하기 위해서 비동기를 함수로 넣어주겠다는 약속을 한 것이다. 필요할 때 함수를 호출할 수 있는 썽크의 형태.

// next 자리에 dispatch 넣어도 똑같음
const thunkMiddleware = (store) => (dispatch) => (action) => {
  typeof action === "function"
    // 비동기 코드
      ? action(store.dispatch, store.getState)
    : dispatch(action);
};

함수를 디스패치할 때, 해당 함수에서 dispatch와 getstate를 파라미터로 받는다. 이 함수를 만들어주는 함수를 thunk라고 한다.
(getState:store에 있는 state에 직접 접근하는 것이 안되기때문에 getState를 통해서 값을 가져오고, dispatch를 통해 값을 변경시킨다.)

실전예제

// store.js
import { createStore, applyMiddleware } from "redux";
import rootReducer from "../example/rootRefucer";
import logger from "redux-logger";
// 1. thunk 추가하기 : action에서 dispatch를 리턴해줄 수 있게 해줌
import thunk from "redux-thunk";

// 2. middleware에 thunk 추가하기
const middleware = [logger, thunk];

const store = createStore(rootReducer, applyMiddleware(...middleware));

export default store;
//types.js
export const FETCH_COMMENTS_REQUEST = "FETCH_COMMENTS_REQUEST";
export const FETCH_COMMENTS_SUCCESS = "FETCH_COMMENTS_SUCCESS";
export const FETCH_COMMENTS_FAILURE = "FETCH_COMMENTS_FAILURE";

//actions.js
import axios from "axios";

import {
  FETCH_COMMENTS_FAILURE,
  FETCH_COMMENTS_REQUEST,
  FETCH_COMMENTS_SUCCESS,
} from "./types";

const fetchCommentRequest = () => {
  return {
    type: FETCH_COMMENTS_REQUEST,
  };
};

const fetchCommentSuccess = (data) => {
  return {
    type: FETCH_COMMENTS_SUCCESS,
    payload: data,
  };
};

const fetchCommentFailure = (error) => {
  return {
    type: FETCH_COMMENTS_FAILURE,
    payload: error,
  };
};

//thunk사용했기 때문에 dispatch를 인자로 넘겨받은 함수를 사용한다.
export const fetchComments = () => {
    return (dispatch) => {
      dispatch(fetchCommentRequest());
      axios
        .get("https://jsonplaceholder.typicode.com/comments")
        .then((res) => dispatch(fetchCommentSuccess(res.data)))
        .catch((error) => fetchCommentFailure(error));
    };
  };

//reducer.js
import {
  FETCH_COMMENTS_FAILURE,
  FETCH_COMMENTS_REQUEST,
  FETCH_COMMENTS_SUCCESS,
} from "./types";

const initialState = {
  comments: [],
  loading: false,
  error: null,
};

const commentsReducer = (state = initialState, action) => {
  switch (action.type) {
    case FETCH_COMMENTS_REQUEST:
      return {
        ...state,
        loading: true,
      };
    case FETCH_COMMENTS_SUCCESS:
      return {
        ...state,
        comments: action.payload,
        loading: true,
      };
    case FETCH_COMMENTS_FAILURE:
      return {
        ...state,
        error: action.payload,
        loading: true,
      };
    default:
      return state;
  }
};

export default commentsReducer;

컴포넌트에 connect로 리덕스 연결하기

import React, { useEffect } from "react";
import { connect } from "react-redux";
import { fetchComments } from "./actions";

function Comments({ fetchComments, loading, comments }) {
  useEffect(() => {
    fetchComments();
  }, [fetchComments]);
  
  const CommentsItems = loading ? (
    <div>is loading...</div>
  ) : (
    comments.map((comment) => <div key={comment.id}>{comment.name}</div>)
  );
  return (
    <div>
      <div>{CommentsItems}</div>
    </div>
  );
}

const mapStateToProps = ({ comments }) => {
  return {
    comments: comments.comments,
  };
};

const mapDispatchToProps = {
  fetchComments,
};

export default connect(mapStateToProps, mapDispatchToProps)(Comments);
profile
Lv.1🌷

0개의 댓글