Redux 장바구니 구현 & TanStack Query와 함께 사용

단단·2024년 8월 8일
0

구현 기능 회고

목록 보기
8/8

장바구니 기능 구현 PR

Redux

  • 클라이언트 상태 관리 라이브러리
  • 복잡한 애플리케이션의 상태를 단일 스토어에서 중앙 집중적으로 관리하고, 상태 변경 흐름을 명확히 한다.
  • 3가지 원칙
    • 애플리케이션의 모든 상태는 하나의 저장소 안에 하나의 객체 트리 구조로 저장한다.
    • 상태는 읽기 전용이다. 상태를 변화시키는 유일한 방법은 무슨 일이 벌어지는 지 묘사하는 액션 객체를 전달하는 방법 뿐이다.
    • 액션에 의해 상태 트리가 어떻게 변화하는 지를 지정하기 위해 순수 리듀서를 작성해야 한다.

Redux와 Tanstack Query 역할 구분

  • Redux 사용 이유
    • 장바구니를 구현할 때 장바구니에 넣은 데이터 상태를 많은 컴포넌트에서 사용해야 했기 때문에 전역 상태 관리가 필요했다.
    • Redux는 단방향 데이터 흐름으로 상태 관리를 하는 라이브러리이기 때문에, React와 함께 사용할 때 디버깅이 쉽고, 복잡한 상태를 편리하게 관리할 수 있다.
  • Redux는 장바구니 UI 업데이트(장바구니 상품 수량, 가격)와 클라이언트 상태 관리에만 사용했고, 상품 목록을 불러오는 데이터 통신은 Tanstack Query로만 진행했다.

장바구니 구현 과정

  • Redux 공식문서에서 추천하는 'Redux Toolkit'을 사용해 코드를 단순화해 개발 편의성을 높이고, 실수를 방지했다.
    0. Redux 세팅
  • 설치: npm install @reduxjs/toolkit react-redux
  • store 파일 작성
import { configureStore } from "@reduxjs/toolkit";
import cartReducer from "./cart";

const store = configureStore({
  reducer: {
    cart: cartReducer,
  },
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export default store;
  • 루트 파일에 Provider 세팅
const queryClient = new QueryClient();
const container: HTMLElement = document.getElementById("app")!;
const root = createRoot(container);

root.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <Provider store={store}>
        <App />
      </Provider>
      <ReactQueryDevtools initialIsOpen={true} />
    </QueryClientProvider>
  </React.StrictMode>
);

1. store에 보관하기 위한 slice(state)와 리듀서 함수 작성

  • PayloadAction: 액션 객체의 구조를 정의할 때 사용한다. 액션의 타입을 지정할 수 있다.
  • createSlice: 리듀서와 액션 생성자를 생성할 수 있다.
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

const cartSlice = createSlice({
  name: "cart",
  initialState: loadCartFromLocalStorage(),
  reducers: {
    addToCart(state, action: PayloadAction<ICartItems>) {
      const newItem = action.payload;
      const existingItem = state.items.find((item) => item.id === newItem.id);

      if (existingItem) {
        existingItem.count += newItem.count;
      } else {
        state.items.push(newItem);
      }
      state.totalAmount = state.items.reduce((total, item) => total + item.price * item.count, 0);

      saveCartToLocalStorage(state);
    },
    removeFromCart(state, action: PayloadAction<number>) {
      const id = action.payload;
      const existingItem = state.items.find((item) => item.id === id);

      if (existingItem) {
        state.totalAmount -= existingItem.price * existingItem.count;
        state.items = state.items.filter((item) => item.id !== id);

        saveCartToLocalStorage(state);
      }
    },
  },
});

export const { addToCart, removeFromCart, clearCart, increaseItemCount, decreaseItemCount } = cartSlice.actions;
export default cartSlice.reducer;

2. useDispatch()와 useSelector()로 slice와 리듀서 함수 사용하기

  • useDispatch: Redux 액션을 store에 보내 상태를 변경할 수 있다.
  • useSelector: Redux store에서 상태를 가져올 수 있다.
import { useDispatch, useSelector } from "react-redux";

const CartList = (): JSX.Element => {
  const dispatch: AppDispatch = useDispatch();
  const { items, totalAmount } = useSelector((state: RootState) => state.cart);

  const handleAddCount = (id: number) => {
    dispatch(increaseItemCount(id));
  };

3. 장바구니 상태를 localStorage에 저장하고, 로드

const loadCartFromLocalStorage = (): ICartState => {
  const savedCart = localStorage.getItem("cartItems");
  if (savedCart) {
    try {
      const { items, totalAmount } = JSON.parse(savedCart);
      return {
        items: items || [],
        totalAmount: totalAmount || 0,
      };
    } catch (error) {
      console.error("Failed to parse cart data from localStorage", error);
      return initialState;
    }
  }
  return initialState;
};

const saveCartToLocalStorage = (state: ICartState) => {
  const cartData = {
    items: state.items,
    totalAmount: state.totalAmount,
  };
  localStorage.setItem("cartItems", JSON.stringify(cartData));
};

트러블 슈팅

  • 문제: console.log(totalAmount)로 디버깅해보니 제대로 업데이트가 안된다고 있음을 발견했다.

  • 원인: 업데이트한 값을 저장하지 않아서 장바구니 totalAmount 초기값은 0을 유지했다.

  • 해결: 업데이트한 값을 localStarage에 저장히고, 저장한 데이터를 불러오는 함수를 만들어 초기값으로 지정했다.

  • 배운 점: 상태를 업데이트한 후엔 localStorage에 저장해 데이터 일관성을 유지해야 한다. 상태 변경 후 저장하지 않으면 페이지를 새로고침했을 때 변경 사항이 반영되지 않을 수 있다.

  • 이외에 Redux를 사용하기 위해 초기에 설정해줘야 할 게 많아 복잡했다. 그래도 store, slice 등을 세팅하고 난 후 상태를 전역에서 사용할 땐 편리했다.

참고: Redux 공식문서
구현 영상

profile
반드시 해내는 프론트엔드 개발자

0개의 댓글