'리덕스 미들웨어'를 사용해서 비동기 작업을 처리해보자.
미들웨어는 액션과 리듀서 사이의 중간자로, 액션을 디스패치했을 때 리듀서에서 이를 처리하기에 앞서 사전에 지정된 작업들을 실행할 수 있게 해준다.
리덕스는 액션 객체가 생성되고, 디스패치를 통해 스토어에 상태 변화를 알리면 리듀서는 정해진 로직에 따른 액션을 처리한 후 새로운 상태값을 반환하는 동기적인 흐름을 통해 이뤄진다.
하지만 외부 데이터를 요청하는 비동기적인 요소들을 처리해야 할 경우가 있을텐데 이때 사용하는 것이 '리덕스 미들웨어'이다.
미들웨어는 스토어를 생성할 때 리덕스 모듈 안에 있는 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에서는 액션생성함수는 객체가 아닌 함수
를 반환해준다.
쉅게말해, 동기와 비동기를 구분하기 위해서 비동기를 함수로 넣어주겠다는 약속을 한 것이다. 필요할 때 함수를 호출할 수 있는 썽크의 형태.
// 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;
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);