오늘은 Redux 파트를 학습하기 전에 이전에 배웠던 내용을 바탕으로 React를 사용하여 쇼핑사이트의 장바구니 기능을 구현하였다. 컴포넌트로 페이지를 구분하고 Side Effect를 고려하면서 코드를 작성하고 로컬과 전역상태를 구분하는 것이 React 자체를 이해하는데 도움이 되었다.
import React, { useState } from 'react'; import Nav from './components/Nav'; import ItemListContainer from './pages/ItemListContainer'; import './App.css'; import { BrowserRouter as Router, Switch, 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); const hadleCartItems = (item) => { const copy = cartItems let isInclude = false copy.forEach((el) => { if(el.itemId === item.itemId) { el.quantity = el.quantity+1; setCartItems(copy); isInclude = true; } }) if(!isInclude) { setCartItems([...cartItems, item]) } } const delteCartItems = (deleteId) => { setCartItems(cartItems.filter((el) => el.itemId !== deleteId)) } const changeCartItems = (items) => { setCartItems(items); } return ( <Router> <Nav cartItemsNum={cartItems.length}/> <Switch> <Route exact={true} path="/"> <ItemListContainer items={items} hadleCartItems={hadleCartItems}/> </Route> <Route path="/shoppingcart"> <ShoppingCart cartItems={cartItems} items={items} delteCartItems={delteCartItems} changeCartItems={changeCartItems}/> </Route> </Switch> </Router> ); } export default App;
import React from 'react'; import Item from '../components/Item'; function ItemListContainer({ items , hadleCartItems }) { const handleClick = (e, id) => { const cartItem = {"itemId": id, "quantity": 1} hadleCartItems(cartItem) } 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;
import React from 'react' export default function Item({ item, handleClick }) { return ( <div key={item.id} className="item"> <img className="item-img" src={item.img} alt={item.name}></img> <span className="item-name">{item.name}</span> <span className="item-price">{item.price}</span> <button className="item-button" onClick={(e) => handleClick(e, item.id)}>장바구니 담기</button> </div> )
import React, { useState } from 'react' import CartItem from '../components/CartItem' import OrderSummary from '../components/OrderSummary' export default function ShoppingCart({ items, cartItems, delteCartItems, changeCartItems }) { 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) => { changeCartItems(cartItems.map((el) => { if(el.itemId === itemId) { return {...el, "quantity": quantity} } else return el })); } const handleDelete = (itemId) => { setCheckedItems(checkedItems.filter((el) => el !== itemId)); delteCartItems(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> ) }
import React from 'react' export default function CartItem({ item, checkedItems, handleCheckChange, handleQuantityChange, handleDelete, quantity }) { return ( <li className="cart-item-body"> <input type="checkbox" className="cart-item-checkbox" onChange={(e) => { handleCheckChange(e.target.checked, item.id) }} checked={checkedItems.includes(item.id) ? true : false} > </input> <div className="cart-item-thumbnail"> <img src={item.img} alt={item.name} /> </div> <div className="cart-item-info"> <div className="cart-item-title" data-testid={item.name}>{item.name}</div> <div className="cart-item-price">{item.price} 원</div> </div> <input type="number" min={1} className="cart-item-quantity" value={quantity} onChange={(e) => { handleQuantityChange(Number(e.target.value), item.id) }}> </input> <button className="cart-item-delete" onClick={() => { handleDelete(item.id) }}>삭제</button> </li > ) }
오늘 스프린트를 통해 React의 작동원리에 대해서 다시 이해할 수 있었고 내일 Redux를 사용하여 동일한 웹페이지를 구현하는 스프린트를 진행할 예정이라 미리 State와 Props로 관리할 데이터들을 파악하여 다음 스프린트 진행 시, 빠르게 파악하는데 도움이 되었다.