웹사이트 검색

Redux Thunk를 사용한 비동기 Redux 작업 이해


소개

기본적으로 Redux의 작업은 동기식으로 전달되며 이는 외부 API와 통신하거나 부작용을 수행해야 하는 사소한 앱에 문제가 됩니다. Redux는 또한 발송되는 액션과 리듀서에 도달하는 액션 사이에 있는 미들웨어를 허용합니다.

부작용과 비동기 작업을 허용하는 두 가지 매우 인기 있는 미들웨어 라이브러리가 있습니다: Redux Saga. 이 게시물에서는 Redux Thunk를 탐색합니다.

Thunk는 연산의 평가/계산을 지연시키기 위해 함수를 사용하는 프로그래밍 개념입니다.

Redux Thunk는 액션 개체 대신 함수를 반환하는 액션 생성자를 호출할 수 있는 미들웨어입니다. 이 함수는 스토어의 디스패치 메서드를 받은 다음 비동기 작업이 완료되면 함수 본문 내에서 일반 동기 작업을 디스패치하는 데 사용됩니다.

이 기사에서는 Redux Thunk를 추가하는 방법과 가상의 Todo 애플리케이션에 어떻게 적용할 수 있는지 알아봅니다.

전제 조건

이 게시물은 여러분이 React와 Redux에 대한 기본 지식이 있다고 가정합니다. Redux를 시작하는 경우 이 게시물을 참조할 수 있습니다.

이 자습서는 완료해야 하고 완료된 작업을 추적하는 가상의 Todo 애플리케이션을 기반으로 합니다. 우리는 create-react-app이 새로운 React 애플리케이션을 생성하는 데 사용되었고 redux, react-redux가 사용되었다고 가정할 수 있습니다. axios가 이미 설치되어 있습니다.

처음부터 Todo 애플리케이션을 구축하는 방법에 대한 자세한 내용은 여기에서 설명하지 않습니다. Redux Thunk를 강조하기 위한 개념적 설정으로 제시됩니다.

redux-thunk 추가

먼저 터미널을 사용하여 프로젝트 디렉토리로 이동하고 프로젝트에 redux-thunk 패키지를 설치합니다.

  1. npm install redux-thunk@2.3.0

참고: Redux Thunk는 14줄의 코드입니다. Redux 미들웨어가 내부적으로 어떻게 작동하는지 알아보려면 여기에서 소스를 확인하세요.

이제 Redux의 applyMiddleware를 사용하여 앱 스토어를 생성할 때 미들웨어를 적용하세요. reduxreact-redux가 포함된 React 애플리케이션에서 index.js 파일은 다음과 같을 수 있습니다.

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import './index.css';
import rootReducer from './reducers';
import App from './App';
import * as serviceWorker from './serviceWorker';

// use applyMiddleware to add the thunk middleware to the store
const store = createStore(rootReducer, applyMiddleware(thunk));

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

이제 Redux Thunk를 가져와 애플리케이션에 적용합니다.

샘플 애플리케이션에서 Redux Thunk 사용

Redux Thunk의 가장 일반적인 사용 사례는 외부 API와 비동기식으로 통신하여 데이터를 검색하거나 저장하는 것입니다. Redux Thunk를 사용하면 외부 API에 대한 요청의 수명 주기를 따르는 작업을 쉽게 디스패치할 수 있습니다.

새 할 일 항목 만들기는 일반적으로 할 일 항목 만들기가 시작되었음을 나타내는 작업을 먼저 발송하는 것과 관련됩니다. 그런 다음 todo 항목이 성공적으로 생성되고 외부 서버에서 반환되면 새 todo 항목으로 다른 작업을 디스패치합니다. 오류가 발생하여 todo가 서버에 저장되지 않는 경우 오류가 있는 작업이 대신 발송될 수 있습니다.

Redux Thunk를 사용하여 이것이 어떻게 달성되는지 봅시다.

컨테이너 구성 요소에서 작업을 가져와 발송합니다.

import { connect } from 'react-redux';
import { addTodo } from '../actions';
import NewTodo from '../components/NewTodo';

const mapDispatchToProps = dispatch => {
  return {
    onAddTodo: todo => {
      dispatch(addTodo(todo));
    }
  };
};

export default connect(
  null,
  mapDispatchToProps
)(NewTodo);

작업은 https://jsonplaceholder.typicode.com/todos를 사용합니다.

import {
  ADD_TODO_SUCCESS,
  ADD_TODO_FAILURE,
  ADD_TODO_STARTED,
  DELETE_TODO
} from './types';

import axios from 'axios';

export const addTodo = ({ title, userId }) => {
  return dispatch => {
    dispatch(addTodoStarted());

    axios
      .post(`https://jsonplaceholder.typicode.com/todos`, {
        title,
        userId,
        completed: false
      })
      .then(res => {
        dispatch(addTodoSuccess(res.data));
      })
      .catch(err => {
        dispatch(addTodoFailure(err.message));
      });
  };
};

const addTodoSuccess = todo => ({
  type: ADD_TODO_SUCCESS,
  payload: {
    ...todo
  }
});

const addTodoStarted = () => ({
  type: ADD_TODO_STARTED
});

const addTodoFailure = error => ({
  type: ADD_TODO_FAILURE,
  payload: {
    error
  }
});

addTodo 작업 생성자가 일반 작업 개체 대신 함수를 반환하는 방법에 주목하십시오. 이 함수는 스토어에서 디스패치 메서드를 받습니다.

함수 본문 내에서 외부 API를 사용하여 할 일 저장을 시작했음을 나타내기 위해 먼저 스토어에 즉각적인 동기 작업을 전달합니다. 그런 다음 Axios를 사용하여 서버에 실제 POST 요청을 합니다. 서버의 성공적인 응답에서는 응답에서 받은 데이터와 함께 동기식 성공 작업을 발송하지만 실패 응답에서는 오류 메시지와 함께 다른 동기식 작업을 발송합니다.

이 경우 JSONPlaceholder와 같은 외부 API를 사용하면 실제 네트워크 지연이 발생하는 것을 볼 수 있습니다. 그러나 로컬 백엔드 서버로 작업하는 경우 실제 사용자가 경험하는 네트워크 지연을 경험하기에는 네트워크 응답이 너무 빨리 발생할 수 있으므로 개발할 때 약간의 인위적인 지연을 추가할 수 있습니다.

// ...

export const addTodo = ({ title, userId }) => {
  return dispatch => {
    dispatch(addTodoStarted());

    axios
      .post(ENDPOINT, {
        title,
        userId,
        completed: false
      })
      .then(res => {
        setTimeout(() => {
          dispatch(addTodoSuccess(res.data));
        }, 2500);
      })
      .catch(err => {
        dispatch(addTodoFailure(err.message));
      });
  };
};

// ...

오류 시나리오를 테스트하기 위해 수동으로 오류를 발생시킬 수 있습니다.

// ...

export const addTodo = ({ title, userId }) => {
  return dispatch => {
    dispatch(addTodoStarted());

    axios
      .post(ENDPOINT, {
        title,
        userId,
        completed: false
      })
      .then(res => {
        throw new Error('addToDo error!');
        // dispatch(addTodoSuccess(res.data));
      })
      .catch(err => {
        dispatch(addTodoFailure(err.message));
      });
  };
};

// ...

완벽을 기하기 위해 다음은 요청의 전체 수명 주기를 처리하기 위해 todo 리듀서가 어떻게 생겼는지에 대한 예입니다.

import {
  ADD_TODO_SUCCESS,
  ADD_TODO_FAILURE,
  ADD_TODO_STARTED,
  DELETE_TODO
} from '../actions/types';

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

export default function todosReducer(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO_STARTED:
      return {
        ...state,
        loading: true
      };
    case ADD_TODO_SUCCESS:
      return {
        ...state,
        loading: false,
        error: null,
        todos: [...state.todos, action.payload]
      };
    case ADD_TODO_FAILURE:
      return {
        ...state,
        loading: false,
        error: action.payload.error
      };
    default:
      return state;
  }
}

getState 탐색

상태에서 디스패치 메서드를 수신하는 것 외에도 Redux Thunk를 사용하여 비동기 작업 작성자가 반환한 함수는 현재 스토어 값을 읽을 수 있도록 스토어의 getState 메서드도 수신합니다.

export const addTodo = ({ title, userId }) => {
  return (dispatch, getState) => {
    dispatch(addTodoStarted());

    console.log('current state:', getState());

    // ...
  };
};

위와 같이 현재 상태가 콘솔에 출력됩니다.

예를 들어:

{loading: true, todos: Array(1), error: null}

getState를 사용하면 현재 상태에 따라 다르게 처리하는 데 유용할 수 있습니다. 예를 들어 앱을 한 번에 4개의 할일 항목으로 제한하려는 경우 상태에 이미 최대 할일 항목이 포함되어 있으면 함수에서 반환할 수 있습니다.

export const addTodo = ({ title, userId }) => {
  return (dispatch, getState) => {
    const { todos } = getState();

    if (todos.length > 4) return;

    dispatch(addTodoStarted());

    // ...
  };
};

위와 같이 앱은 4개의 할 일 항목으로 제한됩니다.

결론

이 튜토리얼에서는 React 애플리케이션에 Redux Thunk를 추가하여 작업을 비동기식으로 디스패치하는 방법을 살펴보았습니다. 이는 Redux 스토어를 활용하고 외부 API에 의존할 때 유용합니다.

React에 대해 자세히 알아보려면 연습 및 프로그래밍 프로젝트에 대한 React 주제 페이지를 살펴보세요.