오늘
![[14기 과제] React(2)-쇼핑몰 커스터마이징](/_next/image?url=https%3A%2F%2Flikelion-blog-images.s3.ap-northeast-2.amazonaws.com%2Fimages%2Fposts%2F14%2Fd7cf51e8-675d-4b6c-938b-5c0b09bb58e9.png&w=3840&q=75)
이번 5주차 세션에서는 Typescript, Tailwindcss, React Router DOM, Context API에 대해 배웠습니다. 이를 활용해 기본적인 React만 사용할 때보다 더 안정적이고 효율적으로 페이지를 관리할 수 있게 되었습니다.
이번 과제는 쇼핑몰 커스터마이징이었습니다. 세션에서 활용한 쇼핑몰 사이트에 다양한 기능을 구현해보았습니다.
우선 Context API로 구현한 장바구니 상태 관리를 Zustand로 리팩토링하였습니다. 기존 Context API를 사용할 때는 CartContext.tsx에서 아래와 같이 Provider를 사용해 장바구니를 관리했습니다.
<CartProvider>
<App />
</CartProvider>하지만 Zustand를 사용하면 Provider없이 바로 관리가 가능합니다.
const cart = useCartStore((state) => state.cart);그리고 기존에는 페이지를 새로고침하면 장바구니의 상품들이 사라지는 문제가 있었습니다. Zustand의 persist를 사용해 이를 해결했습니다.
import { persist } from "zustand/middleware";
export const useCartStore = create<CartStore>()(
persist(
(set) => ({
cart: [],
addToCart: (product) =>
set((state) => ({
cart: [...state.cart, product],
})),
removeFromCart: (index) =>
set((state) => ({
cart: state.cart.filter((_, i) => i !== index),
})),
}),
{
name: "cart-storage",
}
)
);
상품 탭에서는 검색 필터, 카테고리 분류, 정렬 기능을 구현하였습니다. 검색 필터는 아래와 같이 구현하였습니다.
//검색 state
const [search, setSearch] = useState("");
//필터링
const filteredProducts = products.filter((product) =>
product.name.toLowerCase().includes(search.toLowerCase())
);
//검색창 추가
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
/>filteredProducts를 활용해 Products.tsx에서 필터링된 상품만 map으로 출력하여 검색 기능을 구현하였습니다.
카테고리를 분류하기 위해 product.ts에 category 타입을 추가하고 상품마다 카테고리를 추가했습니다.
category: string;그리고 아래와 같이 카테고리 분류 기능을 구현하였습니다.
//category state
const [category, setCategory] = useState("전체");
const categories = ["전체", "상의", "하의", "신발", "가방", "아우터"];
//카테고리 버튼
<button onClick={() => setCategory(item)}>
//카테고리 필터
const matchesCategory =
category === "전체" ||
product.category === category;그리고 상품 디테일에서 상품 목록으로 돌아갈 때 전체 카테고리로 이동하지 않고 직전 카테고리로 이동하도록 하였습니다.
//ProductCard.tsx
state={{ category: currentCategory }}
//ProductDetail.tsx
const previousCategory =
location.state?.category || "전체";
//목록으로 버튼
<Link
to="/products"
state={{ category: previousCategory }}
>
//Products.tsx
const [category, setCategory] = useState(
location.state?.category || "전체"
);location을 사용하여 이전 카테고리 위치를 기억하도록 하였습니다.
그리고 가격에 따라 상품들이 정렬되는 기능을 구현했습니다.
//sort state
const [sort, setSort] = useState("default");
//정렬 배열
const sortedProducts = [...filteredProducts].sort((a, b) => {
if (sort === "price-low") {
return a.price - b.price;
}
if (sort === "price-high") {
return b.price - a.price;
}
return 0;
});
//select 버튼 추가
<select
value={sort}
onChange={(e) => setSort(e.target.value)}
className="mb-6 px-4 py-2 border rounded-lg cursor-pointer"
>
<option value="default">문신사 추천순</option>
<option value="price-low">가격 낮은 순</option>
<option value="price-high">가격 높은 순</option>
</select>앞서 구현한 검색, 카테고리 필터와 함께 sort 배열을 활용하여 정렬 기능을 구현했습니다.
검색, 카테고리, 정렬 기능을 모두 사용한 모습입니다.
장바구니 탭에서는 장바구니 비우기 버튼, 수량 조절 버튼, 결제하기 버튼을 구현했습니다.
우선 장바구니 비우기 버튼은 cartStore.ts에 장바구니를 비우는 clearCart 함수를 만들어서 구현했습니다.
clearCart: () =>
set({
cart: [],
}),수량 조절 버튼은 Product에 수량 개념을 도입하여 구현했습니다.
interface CartItem extends Product {
quantity: number;
}
removeFromCart: (index) =>
set((state) => ({
cart: state.cart.filter((_, i) => i !== index),
})),
increaseQuantity: (index) =>
set((state) => ({
cart: state.cart.map((item, i) =>
i === index
? { ...item, quantity: item.quantity + 1 }
: item
),
})),
decreaseQuantity: (index) =>
set((state) => ({
cart: state.cart.map((item, i) =>
i === index && item.quantity > 1
? { ...item, quantity: item.quantity - 1 }
: item
),
})),기존에 있던 삭제 버튼과 수량 조절 버튼 모두 index방식을 사용하였습니다. 이를 통해 같은 상품을 여러 번 담은 후 삭제 버튼을 누르면 모두 사라지는 문제를 해결했습니다. 마지막으로 결제 버튼은 아래와 같이 구현했습니다.
<button
onClick={() => {
if (confirm("정말 결제하시겠습니까?")) {
alert("결제가 완료되었습니다!");
clearCart();
}
}}
>
결제하기
</button>
댓글 0