이번과제는 머리를 많이 썼던 것 같다 .. 실시간 세션 전에 하나의 문제가 발생해 시간내에 해결하지 못했지만 다시 복습을 통해 완벽하게 이해할 수 있었다. 페어분께도 도움을 많이 받아 여러 궁금증도 해결하고 정말 의미있던 과제였다 !
구현해야할 과제는 아래와 같다
Bare minimum requirements
- react-router-dom을 이용해 Client Side Routing하는 방법을 학습합니다.
- useState를 이용해 상태를 사용하는 방법을 학습합니다.
- 쇼핑몰 애플리케이션의 주요 기능을 구현하세요.
- [장바구니 담기] 버튼을 이용해 장바구니에 해당 상품이 추가되도록 구현하세요.
- 장바구니 내 [삭제] 버튼을 이용해 장바구니의 상품이 제거되도록 구현하세요.
- 장바구니 내에서 각 아이템 개수를 변경할 수 있도록 구현하세요.
- 장바구니의 상품 개수의 변동이 생길 때마다, 상단 내비게이션 바에 상품 개수가 업데이트되도록 구현하세요.
페이지 전환
페이지 전환은 react-router-dom을 이용해 Client Side Routing을 구현하였습니다. 페이지가 전환되지 않는 Single Page App임에도 주소창이 변화되는 것을 확인하세요.
- <App> 루트 컴포넌트
- <Router>, <Routes>, <Route>로 구분을 지어줍니다.
- <Nav> 내비게이션 바
- SPA 내에서 화면 전환에 따라 URL를 업데이트하기 위해 <Link> 컴포넌트를 사용합니다.
페이지 전환은 모두 구현이 완료된 상태였고 과제를 시작하기 전 컴포넌트의 관계에 대해 정리하여 과제를 진행하였다.
컴포넌트
TODO: 장바구니에 추가 및 상품 개수 업데이트
다음과 같이 작동해야 합니다.
- 메인 화면에서 [장바구니 담기] 버튼을 누른 후, 장바구니 페이지로 이동하면 상품이 담겨있어야 합니다.
- 장바구니 페이지에서 장바구니에 담긴 각 아이템의 개수를 변경할 수 있어야 합니다.
- 내비게이션 바에 상품 개수가 즉시 표시되어야 합니다.
첫 번째로 구현한 부분은 내비게이션 바에 상품 개수를 표시하는 부분이었다.
State.js
export const initialState =
{
"items": [
{
"id": 1,
"name": "노른자 분리기",
"img": "../images/egg.png",
"price": 9900
},
{
"id": 2,
"name": "2020년 달력",
"img": "../images/2020.jpg",
"price": 12000
},
{
"id": 3,
"name": "개구리 안대",
"img": "../images/frog.jpg",
"price": 2900
},
{
"id": 4,
"name": "뜯어온 보도블럭",
"img": "../images/block.jpg",
"price": 4900
},
{
"id": 5,
"name": "칼라 립스틱",
"img": "../images/lip.jpg",
"price": 2900
},
{
"id": 6,
"name": "잉어 슈즈",
"img": "../images/fish.jpg",
"price": 3900
},
{
"id": 7,
"name": "웰컴 매트",
"img": "../images/welcome.jpg",
"price": 6900
},
{
"id": 8,
"name": "강시 모자",
"img": "../images/hat.jpg",
"price": 9900
}
],
"cartItems": [
{
"itemId": 1,
"quantity": 1
},
{
"itemId": 5,
"quantity": 7
},
{
"itemId": 2,
"quantity": 3
}
]
}
App.js
import React, { useState } from 'react';
import Nav from './components/Nav';
import ItemListContainer from './pages/ItemListContainer';
import './App.css';
import './variables.css';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import ShoppingCart from './pages/ShoppingCart';
import { initialState } from './assets/state';
function App() {
const [items, setItems] = useState(initialState.items);
const [cartItems, setCartItems] = useState(initialState.cartItems);
return (
<Router>
<Nav cartItems ={cartItems}/>
<Routes>
<Route path="/" element={<ItemListContainer items={items} cartItems={cartItems} setCartItems={setCartItems}/>} />
<Route
path="/shoppingcart"
element={<ShoppingCart cartItems={cartItems} items={items} setCartItems={setCartItems} />}
/>
</Routes>
<img
id="logo_foot"
src={`${process.env.PUBLIC_URL}/codestates-logo.png`}
alt="logo_foot"
/>
</Router>
);
}
export default App;
Nav.js
import React from 'react';
import { Link } from 'react-router-dom';
function Nav( {cartItems}) {
return (
<div id="nav-body">
<span id="title">
<img id="logo" src="../logo.png" alt="logo" />
<span id="name">CMarket</span>
</span>
<div id="menu">
<Link to="/">상품리스트</Link>
<Link to="/shoppingcart">
장바구니<span id="nav-item-counter">{cartItems.length}</span>
</Link>
</div>
</div>
);
}
export default Nav;
부모 컴포넌트인 App.js 로 부터 props로 cartItems를 전달 받아왔다. cartItems 에는 장바구니에 들어가있는 데이터 더미가 존재했고 장바구니 수는 간단하게 cartItems.length 를 통해 구현할 수 있었다.
ItemListContainer.js
import React from 'react';
import Item from '../components/Item';
function ItemListContainer({ items ,cartItems ,setCartItems}) {
const handleClick = (itemId) => {
// 장바구니 버튼 담기를 눌렀을 때 실제 cartItems 에 들어가야 함 . cartItems의 상태 변경은 setCartItems를 통해
// itemId 랑 비교해서 장바구니 리스트에 없을 시에만 장바구니에 추가해야함 . 있는 경우는 무시
//find() 메서드는 주어진 판별 함수를 만족하는 첫 번째 요소의 값을 반환합니다. 그런 요소가 없다면 undefined를 반환합니다.
//find() 를 활용해볼것임
//없는 것을 알고싶으니까 ! 를 통해 찾아냄
if(!cartItems.find(item => item.itemId === itemId)){
setCartItems([...cartItems,
{
"itemId": itemId,
"quantity": 1
},])
}
}
return (
<div id="item-list-container">
<div id="item-list-body">
<div id="item-list-title">쓸모없는 선물 모음</div>
{items.map((item, idx) => <Item item={item} key={idx} handleClick={handleClick} />)}
</div>
</div>
);
}
export default ItemListContainer;
장바구니 담기 버튼을 클릭했을 시 실행하는 함수인 handleClick 에 인자로 itemId를 받아 find 메서드를 활용해 인자로 들어온 itemId가 cartItems에 없을 때 (!를 활용 find 메서드가 undefined 일때) 즉 장바구니에 없을 때에만 상태 갱신 함수인 setCartItems에 원래 기존의 장바구니 아이템배열에 새롭게 추가할 객체를 붙여준다.
TODO: 장바구니로부터 제거
- 장바구니 페이지에서 [삭제] 버튼을 누른 후, 해당 상품이 목록에서 삭제되어야 합니다.
- 내비게이션 바에 상품 개수가 즉시 표시되어야 합니다.
ShoppingCart.js
import React, { useState } from 'react'
import CartItem from '../components/CartItem'
import OrderSummary from '../components/OrderSummary'
export default function ShoppingCart({ items, cartItems , setCartItems}) {
const [checkedItems, setCheckedItems] = useState(cartItems.map((el) => el.itemId))
const handleCheckChange = (checked, id) => {
if (checked) {
setCheckedItems([...checkedItems, id]);
}
else {
setCheckedItems(checkedItems.filter((el) => el !== id));
}
};
const handleAllCheck = (checked) => {
if (checked) {
setCheckedItems(cartItems.map((el) => el.itemId))
}
else {
setCheckedItems([]);
}
};
const handleQuantityChange = (quantity, itemId) => {
//장바구니 변경이 없는것들은 그대로 추가해주고 , 새로 수량이 바뀐 itemId 는 새롭게 변경시켜주기 위함
const newCartitems = cartItems.filter(e => e.itemId !== itemId)
setCartItems([...newCartitems,
{
"itemId": itemId,
"quantity": quantity
},])
}
// 삭제는 간단함 setCartItems에 삭제 버튼이 눌린 것을 제외하고 상태를 갱신해주면 됨
const handleDelete = (itemId) => {
setCheckedItems(checkedItems.filter((el) => el !== itemId))
setCartItems(cartItems.filter(e => e.itemId !==itemId));
}
const getTotal = () => {
let cartIdArr = cartItems.map((el) => el.itemId)
let total = {
price: 0,
quantity: 0,
}
for (let i = 0; i < cartIdArr.length; i++) {
if (checkedItems.indexOf(cartIdArr[i]) > -1) {
let quantity = cartItems[i].quantity
let price = items.filter((el) => el.id === cartItems[i].itemId)[0].price
total.price = total.price + quantity * price
total.quantity = total.quantity + quantity
}
}
return total
}
const renderItems = items.filter((el) => cartItems.map((el) => el.itemId).indexOf(el.id) > -1)
const total = getTotal()
return (
<div id="item-list-container">
<div id="item-list-body">
<div id="item-list-title">장바구니</div>
<span id="shopping-cart-select-all">
<input
type="checkbox"
checked={
checkedItems.length === cartItems.length ? true : false
}
onChange={(e) => handleAllCheck(e.target.checked)} >
</input>
<label >전체선택</label>
</span>
<div id="shopping-cart-container">
{!cartItems.length ? (
<div id="item-list-text">
장바구니에 아이템이 없습니다.
</div>
) : (
<div id="cart-item-list">
{renderItems.map((item, idx) => {
const quantity = cartItems.filter(el => el.itemId === item.id)[0].quantity
return <CartItem
key={idx}
handleCheckChange={handleCheckChange}
handleQuantityChange={handleQuantityChange}
handleDelete={handleDelete}
item={item}
checkedItems={checkedItems}
quantity={quantity}
/>
})}
</div>
)}
<OrderSummary total={total.price} totalQty={total.quantity} />
</div>
</div >
</div>
)
}
handleQuantityChange 함수는 fitler 를 통해 장바구니 변경이 없을 때는 그대로 상태를 추가해주고 , 새로 수량이 바뀐 것만 인자로 받아온 quentity 로 바뀌게 코드를 작성했다.handleDelete 함수는 fitler 메서드를 활용해 삭제 버튼 클릭시 선택된 id 를 cartItmes의 itemId 와 비교해 일치하지 않은 것들만 다시 setCartItems 인자로 전달해주어 즉 삭제버튼이 눌리지 않은것들만 남게 코드를 작성했다.
완성 결과
'코드스테이츠44기 프론트엔드' 카테고리의 다른 글
솔로프로젝트 - Coz Shopping 프로젝트 계획 및 요구사항 / 헤더 푸터 구현 (0) | 2023.05.29 |
---|---|
cmarket_redux 풀어보기 (1) | 2023.04.25 |
섹션2를 마무리하며 KTP회고 (0) | 2023.04.10 |
Section 2 기술면접 준비 (0) | 2023.04.10 |
Web Server 기초 (0) | 2023.04.06 |