[Redux-Thunk] Redux에서의 비동기 처리

Donggu(oo)·2023년 5월 15일

Redux

목록 보기
6/6
post-thumbnail

1. Redux에서의 비동기 처리


  • Redux에서는 비동기 처리 로직을 reducer에서 실행하면 안 된다. 왜냐하면 reducer는 state에 접근해 상태를 변화시키는 작업을 수행하는데 여기에 비동기 처리 로직이 들어간다면 정의한 로직들의 순차적 실행을 보장할 수 없고, 그렇데 되면 state의 상태를 추적할 수 없기 때문이다.

1) Redux-Thunk

  • 리덕스를 사용하는 어플리케이션에서 비동기 작업을 처리 할 때 사용하는 미들웨어이며, 이 미들웨어는 객체 대신 함수를 생성하는 액션 생성함수를 작성 할 수 있게 해준다.

  • 일반 액션 생성자는 액션 객체를 생성하는 작업을 하는데, 특정 액션이 몇 초 뒤에(ex.서버에서 데이터를 불러온 후) 실행되게 하거나, 현재 상태에 따라 액션이 무시되게 하려면 일반 액션 생성자로는 실행할 수 없다.

2) thunk란?

  • 특정 작업을 나중에 하도록 미루기 위해 함수 형태로 감싼것을 칭한다.

  • 예를 들어, const x = 1 + 2;는 실행 즉시 return 되지만, const foo = () => 1 + 2; 함수 형태로 만들면 함수가 호출되어야 실행되는 원리다.

2. Redux-Thunk 적용해보기


  • Redux Toolkit을 설치했다면 별도로 Redux-Thunk를 설치할 필요없다.

  • 기존에는 App.js에서 비동기 작업을 하는 코드가 위치해 있었다.

// App.js
import styled from 'styled-components';
import MainHeader from './components/Header/MainHeader';
import Cart from './components/MyCart/Cart';
import Products from './components/ItemList/Products';
import { useDispatch, useSelector } from 'react-redux';
import { useEffect } from 'react';
import axios from 'axios';
import { showNotification } from './redux/slice/uiSlice';
import Notification from './components/UI/Notification';

const ItemArea = styled.div`
  display: flex;
  justify-content: center;
  align-items: flex-start;
  column-gap: 50px;
`;

// 렌더링 될 때 값이 변경되지 않도록 컴포넌트 외부에서 정의
let isInitial = true;

function App() {
  const dispatch = useDispatch();
  const showCart = useSelector(state => state.uiReducer.cartIsVisible);
  const cart = useSelector(state => state.cartReducer);
  const notification = useSelector(state => state.uiReducer.notification)

  useEffect(() => {
    const putCartItem = async () => {
      // 전송중
      dispatch(showNotification({
        status: 'pending',
        title: 'Sending...',
        message: 'Sending cart data!'
      }));
      try {
        await axios.put(`https://redux-http-97631-default-rtdb.asia-southeast1.firebasedatabase.app/cart.json`, cart)
        dispatch(showNotification({
          status: 'success',
          title: 'Success!',
          message: 'Sending cart data successfully!'
        }));
      } catch (err) {
        dispatch(showNotification({
          status: 'error',
          title: 'Error!',
          message: 'Sent cart data failed!'
        }));
      }
    }

    // 처음 렌더링 될 때 put 요청이 실행되므로 장바구니가 비어있는 상태로 오버라이딩 되는 것을 막기 위해
    // putCartItem이 실행되기 전 return하여 차단
    if (isInitial) {
      isInitial = false;
      return
    }

    putCartItem();
  }, [cart, dispatch])

  return (
    <>
      {notification && (
        <Notification
          status={notification.status}
          title={notification.title}
          message={notification.message}
        />
      )}
      <MainHeader />
      <ItemArea>
        <Products />
        {showCart && <Cart />}
      </ItemArea>
    </>
  );
}

export default App;
  • App.js에서는 dispatch를 통해 비동기 작업 함수를 dispatch 하도록 변경 할 수 있다.
// cartSlice.js

export const sendCartData = (cart) => {
  // 함수형태로 감싸줌
  return async (dispatch) => {
    dispatch(showNotification({
      status: 'pending',
      title: 'Sending...',
      message: 'Sending cart data!'
    }));
    try {
      await axios.put(`https://redux-http-97631-default-rtdb.asia-southeast1.firebasedatabase.app/cart.json`, cart)
      dispatch(showNotification({
        status: 'success',
        title: 'Success!',
        message: 'Sending cart data successfully!'
      }));
    } catch (err) {
      dispatch(showNotification({
        status: 'error',
        title: 'Error!',
        message: 'Sent cart data failed!'
      }));
    }
  }
}
// App.js
import styled from 'styled-components';
import MainHeader from './components/Header/MainHeader';
import Cart from './components/MyCart/Cart';
import Products from './components/ItemList/Products';
import { useDispatch, useSelector } from 'react-redux';
import { useEffect } from 'react';
import Notification from './components/UI/Notification';
import { sendCartData } from './redux/slice/cartSlice';

const ItemArea = styled.div`
  display: flex;
  justify-content: center;
  align-items: flex-start;
  column-gap: 50px;
`;

// 렌더링 될 때 값이 변경되지 않도록 컴포넌트 외부에서 정의
let isInitial = true;

function App() {
  const dispatch = useDispatch();
  const showCart = useSelector(state => state.uiReducer.cartIsVisible);
  const cart = useSelector(state => state.cartReducer);
  const notification = useSelector(state => state.uiReducer.notification)

  useEffect(() => {
    // 처음 렌더링 될 때 put 요청이 실행되므로 장바구니가 비어있는 상태로 오버라이딩 되는 것을 막기 위해
    // putCartItem이 실행되기 전 return하여 차단
    if (isInitial) {
      isInitial = false;
      return;
    }
    dispatch(sendCartData(cart))
  }, [cart, dispatch])

  return (
    <>
      {notification && (
        <Notification
          status={notification.status}
          title={notification.title}
          message={notification.message}
        />
      )}
      <MainHeader />
      <ItemArea>
        <Products />
        {showCart && <Cart />}
      </ItemArea>
    </>
  );
}

export default App;

전체 코드

profile
FE Developer

0개의 댓글