<React> Redux 주의점 및 비동기작업은 어떻게 처리해야 하나요? (부제 : thunk란?)

·2023년 8월 15일
0

React

목록 보기
19/23
post-thumbnail

들어가기에 앞서

리듀서 함수는 순수함수여야만 하며, 사이드 이펙트가 없고 동기식으로 작동되어야 합니다.

즉, 리듀서 함수는 이전 상태 및 작업인 리덕스 리듀서의 경우에 Input이 꼭 필요하며, Output을 반환합니다.

Input(Olid State + Action) ➡️ Output(New State)

이 방식은 리덕스 리듀서 뿐만 아니라 useReducer Hook에도 동일하게 적용된다.

이것은 리덕스가 아닌, 리듀서 개념입니다.
Input을 받고, Output을 생성하는 순수한 동기식 함수입니다.

Redux로 작업할 때 비동기적으로 할 수 없나요?

리덕스로 작업할 때 보내는 HTTP 요청이나 여러 의도적인 사이드 이펙트가 필요할 때 비동기적으로 할 수 없는지에 대한 궁금증이 생길 것이다.

리덕스로 작업할 때, 비동기 코드를 어디에 넣어야 할까요?

  1. useEffect를 사용하여 컴포넌트에 직접 비동기 코드를 작성하여 처리하는 방법
  2. action creator 안에 비동기 코드를 작성하는 방법.

1번과 같은 방법으로 비동기적으로 사용할 수 있습니다. 아래 Thunk부분에서 해결할 수 있습니다.

Thunk란?

Thunk 함수는 컴퓨터 프로그래밍에서 사용되는 개념으로, 다른 함수를 감싸거나 또는 다른 함수를 반환하는 함수를 의미합니다.
Redux와 같은 상태 관리 라이브러리에서 비동기 작업을 처리하거나 복잡한 동작을 관리하기 위해 사용됩니다.

Thunk는 다른 작업이 완료될 때까지 작업을 지연시키는 단순한 함수라고 할 수 있습니다.
Thunk는 주로 비동기 작업을 처리하고, Redux Toolkit에서 제공하는 기능 중 하나입니다. (예시 - 네트워크 통신 관련 코드를 작성할 때, thunk를 사용)
Redux에서 비동기 작업을 처리하는 데 유용합니다.

Rudex Toolkit의 Thunk는 action creators를 확장한 형태로 볼 수 있다.
일반적인 Redux action은 단순한 객체를 반환하지만, Thunk action creator는 함수를 반환합니다.

Thunk action creator로 반환되는 함수는 Redux store의 dispatch와 getState 함수를 인자로 받아서 사용할 수 있습니다.
이렇게 함으로써 비동기 작업을 수행할 수 있고, 필요한 경우에 state를 업데이트하거나 추가 작업을 수행할 수 있습니다.

Thunk를 사용하면 API호출 및 데이터 가져오기 등의 비동기 작업을 더욱 쉽게 관리할 수 있습니다.
또한 Thunk 내에서 비동기 작업의 state 변화를 추적하고, 성공과 실패에 따른 다양한 action을 dispatch하여 state 업데이트를 처리할 수 있습니다.

Redux Toolkit에서 Thunk를 사용하려면, createAsyncThunk라는 유틸리티 함수를 사용하여 Thunk action creator를 생성하고, 해당 Thunk action creator를 Redux의 createSlice 또는 createReducer와 함께 사용하여 state 업데이트를 처리할 수 있습니다.
이렇게 함으로써 비동기 작업과 관련된 보일러플레이트 코드를 줄이고, 보다 효율적으로 state 관리를 할 수 있습니다.

즉시 번환되지 않는 action을 원할 때, Thunk로 action creator를 작성할 수 있습니다.

import { cartActions } from "./cartSlice";
import { uiActions } from "./uiSlice";

// thunk함수 생성. createAsyncThunk 함수 사용하지 않음.
// 비동기 작업을 할 때 유용합니다. 예시로 네트워크 관련 통신 코드는 thunk로 작성합니다.
// 해당 함수는 다른 함수를 반환합니다.

export const fetchCartData = () => {
  return async (dispatch) => {
    const fetchData = async () => {
      const response = await fetch(
        `https://react-http-prac-a6b96-default-rtdb.asia-southeast1.firebasedatabase.app/cart.json`
      );

      if (!response.ok) {
        throw new Error("fetch에 실패하였습니다..");
      }

      const data = await response.json();
      console.log("data : ", data);
      return data;
    };

    try {
      const cartGetData = await fetchData();
      // dispatch(cartActions.replaceCart(cartGetData));
      dispatch(
        cartActions.replaceCart({
          items: cartGetData.items || [],
          totalQuantity: cartGetData.totalQuantity,
        })
      );
    } catch (error) {
      await dispatch(
        uiActions.showNotification({
          status: "Error",
          title: "Not success data",
          message: "GET fetch 실패했습니다.",
        })
      );
    }
  };
};

export const sendCartData = (cart) => {
  return async (dispatch) => {
    dispatch(
      uiActions.showNotification({
        status: "pending",
        title: "데이터 전송중...",
        message: "장바구니 데이터 전송중!!!",
      })
    );

    const sendRequest = async () => {
      const response = await fetch(
        "https://react-http-prac-a6b96-default-rtdb.asia-southeast1.firebasedatabase.app/cart.json",
        {
          method: "PUT",
          body: JSON.stringify({
            items: cart.items,
            totalQuantity: cart.totalQuantity,
          }),
        }
      );

      if (!response.ok) {
        throw new Error("데이터 전송 실패!");
      }
    };

    try {
      await sendRequest();
      dispatch(
        uiActions.showNotification({
          status: "success",
          title: "Success!",
          message: "Success send cart data!!!",
        })
      );
    } catch (error) {
      dispatch(
        uiActions.showNotification({
          status: "Error",
          title: "Not success data",
          message: "Cart 데이터 전송 실패했습니다.",
        })
      );
    }
  };
};

1개의 댓글

comment-user-thumbnail
2023년 8월 15일

많은 도움이 되었습니다, 감사합니다.

답글 달기