Redux-Thunk (middleware)

이성진·2020년 7월 4일
0

React는 UI를 작성하기 위한 자바스크립트 라이브러리입니다. React는 Redux와 자주 사용되곤 합니다. Redux는 전역 상태를 관리하기 위한 라이브러리입니다. 이 두 개의 라이브러리를 사용하여도 비동기적으로 API(Back-end)를 호출하는 것 또는 다른 사이드 이펙트를 관리하는 데 많은 어려움이 있습니다.

리덕스 미들웨어?: 리덕스 앱에서 '사이드 이펙트'를 처리하기 위해 사용하는 것

사이드 이펙트?: 우리가 작성한 코드가 외부 세계(Back-end, APIs)와 영향을 주고 받는 일

가장 대표적인 사이드 이펙트가 비동기적으로 API를 호출할 때입니다.

이 글에서는 이 사이드 이펙트를 어떻게 다루는 지에 대해 설명할 것입니다.


사이드 이펙트가 무엇이 문제길래 처리를 위한 미들웨어까지 생겨나게 된 것일까?

리덕스의 핵심 원칙: 스토어의 상태 변화를 기술하는 리듀서는 순수 함수로 작성되어야 한다
순수 함수?: Input이 같으면 Output도 항상 같아야 한다는 원칙

어떤 함수가 사이드 이펙트를 가지고 있습니다. 이는 외부와 데이터를 주고 받는 다는 뜻이고 결국 이 함수는 내부와 외부의 상태에 영향을 받게 됩니다. 이는 리덕스의 핵심 원칙에 위배됩니다.
이처럼 순수하지 못한 작업으로 인한 문제를 사전에 방지하고자 리덕스 미들웨어로 비동기 작업을 합니다.


Redux-thunk

Redux-thunk는 기본적인 리덕스 사이드 이펙트 로직(API 요청과 같은 간단한 비동기 로직)을 위한 미들웨어로 추천됩니다.

리듀서는 총 3가지의 상태를 가집니다.
1. 요청을 보냈을 때
2. 요청이 성공했을 때
3. 요청이 실패했을 때


여기 부터는 다른 곳의 예제를 인용하겠습니다.


우리에게 필요한 일은 5개 입니다.

1. thunk 다운로드

npm install redux-thunk

2. configuring store에 thunk 미들웨어 추가하기 (configureStroe.js)

import { applyMiddleware, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './appReducers'; // Your Reducer

export function configureStore(initialState) {
  const middleware = [thunk];

  const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
  const store = createStore(rootReducer, initialState, composeEnhancers(applyMiddleware(...middleware)));
  return store;
}

8~9 라인은 redux devtools을 설정하는 부분입니다. 나중에 문제가 생겼을 때 해결할 수 있도록 도움을 줄 말한 기능들을 제공합니다.

3. 액션 만들기 (redux-thunk/actions.js)

import Api from "../api"

export const LOAD_USERS_LOADING = 'REDUX_THUNK_LOAD_USERS_LOADING';
export const LOAD_USERS_SUCCESS = 'REDUX_THUNK_LOAD_USERS_SUCCESS';
export const LOAD_USERS_ERROR = 'REDUX_THUNK_LOAD_USERS_ERROR';

export const loadUsers = () => dispatch => {
  dispatch({ type: LOAD_USERS_LOADING });
  Api.getUsers()
    .then(response => response.json())
    .then(
    	data => dispatch({ type: LOAD_USERS_SUCCESS, data }),
    	error => dispatch({ type: LOAD_USERS_ERROR, error: error.message || 'Unexpected Error!!!' })
  )
};

액션 생성자도 따로 분리해놓는 것을 추천합니다. 그러나 간단한 경우에는 망설임 없이 한 파일에 액션을 만드는 것도 상관없습니다.

4. 리듀서 만들기 (redux-thunk/reducer.js)

import {LOAD_USERS_ERROR, LOAD_USERS_LOADING, LOAD_USERS_SUCCESS} from "./actions";

const initialState = {
  data: [],
  loading: false,
  error: ''
};

export default function reduxThunkReducer(state = initialState, action) {
  switch (action.type) {
    case LOAD_USERS_LOADING: {
      return {
        ...state,
        loading: true,
        error:''
      };
    }
    case LOAD_USERS_SUCCESS: {
      return {
        ...state,
        data: action.data,
        loading: false
      }
    }
    case LOAD_USERS_ERROR: {
      return {
        ...state,
        loading: false,
        error: action.error
      };
    }
    default: {
      return state;
    }
  }
}

5. 리덕스와 연결된 컴포넌트 만들기 (redux-thunk/UsersWithReduxThunk.js)

import * as React from 'react';
import { connect } from 'react-redux';
import {loadUsers} from "./actions";
class UsersWithReduxThunk extends React.Component {
   componentDidMount() {
       this.props.loadUsers();
   };
   render() {
       if (this.props.loading) {
           return <div>Loading</div>
       }
       if (this.props.error) {
           return <div style={{ color: 'red' }}>ERROR: {this.props.error}</div>
       }
       return (
           <table>
               <thead>
                   <tr>
                       <th>First Name</th>
                       <th>Last Name</th>
                       <th>Active?</th>
                       <th>Posts</th>
                       <th>Messages</th>
                   </tr>
               </thead>
               <tbody>
               {this.props.data.map(u =>
                   <tr key={u.id}>
                       <td>{u.firstName}</td>
                       <td>{u.lastName}</td>
                       <td>{u.active ? 'Yes' : 'No'}</td>
                       <td>{u.posts}</td>
                       <td>{u.messages}</td>
                   </tr>
               )}
               </tbody>
           </table>
       );
   }
}
const mapStateToProps = state => ({
   data: state.reduxThunk.data,
   loading: state.reduxThunk.loading,
   error: state.reduxThunk.error,
});
const mapDispatchToProps = {
   loadUsers
};
export default connect(
   mapStateToProps,
   mapDispatchToProps
)(UsersWithReduxThunk);

간단한 예제를 만들려고 노력했지만, 끔찍해 보인다는 거 이해합니다. :)

로딩 표시

데이터

에러

Reference

https://levelup.gitconnected.com/loading-data-in-react-redux-thunk-redux-saga-suspense-hooks-666b21da1569

profile
개발보다 회사 매출에 영향력을 주는 개발자

0개의 댓글