코드스테이츠의 과제로 쇼핑몰 애플리케이션의 주요 기능을 구현했다.
그 과정에서 app.js, ItemListContainer.js, ShoppingCart.js 파일의 코드를 자세하게 정리해보려고 한다.
먼저 전체 진행 과정은 아래와 같다.
1. 장바구니의 기능 구현하기
- [장바구니 담기] 버튼을 이용해 장바구니에 해당 상품이 추가되도록 구현
- 장바구니 내 [삭제] 버튼을 이용해 장바구니의 상품이 제거되도록 구현
- 장바구니의 상품 갯수의 변동이 생길 때마다, 상단 내비게이션 바에 상품 갯수가 업데이트되도록 구현
2. app.js 파일에서 state와 메소드를 생성하고 웹 페이지인 ItemListContainer와 ShoppingCart에 props으로 전달
3. itemListContainer와 ShoppingCart에서는 cartItem, Item, Nav, OrderSummary 컴포넌트를 사용
4. 상품 목록(items)과 장바구니 목록(cartItems) 상태로 관리하기 위해 React의 Hook을 사용
// 상품 목록 (items)
{
"id": 1,
"name": "노른자 분리기",
"img": "../images/egg.png",
"price": 9900
}
// 장바구니 목록 (cartItems)
{
"itemId": 1,
"quantity": 1
}
5. 상품 목록과 장바구니 목록의 초기값은 initialState 객체에 저장됨
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에서 상태 관리
1. 상태 관리 : 상품 목록(items)과 장바구니 목록(cartItems)을 app.js에서 관리
2. 장바구니 기능을 구현하여 상태를 변경하기 위한 메소드 제작
3. 상태와 메소드를 props로 하위 컴포넌트에 전달하기
코드 작성
- 중첩 구조 분해를 활용하여 itemId와 quantity의 키와 값을 간단하게 전달함 { itemId, quantity }
- 상태(State)뿐 아니라 상태를 변경하는 함수(setState)를 담은 메소드를 props로 함께 전달하기
- 덕분에 다른 컴포넌트에서 상태(State)를 변경할 수 있음
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() {
// items와 cartItems을 state로 관리
const [items, setItems] = useState(initialState.items);
const [cartItems, setCartItems] = useState(initialState.cartItems);
// 장바구니에 아이템을 추가하여 cartItems에 넣는 메소드
const addToCart = (itemId) => {
const found = cartItems.filter((el) => el.itemId === itemId)[0]
// 이미 장바구니에 있는 상품을 추가하는 경우, 해당 요소의 quantity의 숫자를 1 올려주기
if (found) {
setQuantity(itemId, found.quantity + 1)
}
else { // 장바구니에 없는 상품을 추가할 경우, cartItems에 새로운 엘리먼트로 추가하기
setCartItems([...cartItems, {
itemId,
quantity: 1
}])
}
}
// 이미 장바구니에 있는 상품의 cartItems의 quantity를 변경하는 메소드
const setQuantity = (itemId, quantity) => {
// itemId로 배열에서 해당 상품을 찾고, 그것의 인덱스를 구하기
const found = cartItems.filter((el) => el.itemId === itemId)[0]
const idx = cartItems.indexOf(found)
// 배열에 삽입할 객체 형태의 엘리먼트 선언하기
const cartItem = {
itemId,
quantity
}
// quantity값이 변경되었으므로 기존의 엘리먼트를 삭제하고 새로운 엘리먼트 삽입
setCartItems([
...cartItems.slice(0, idx),
cartItem,
...cartItems.slice(idx + 1)
])
}
// 상품을 장바구니에서 삭제하는 메소드
const handleDelete = (itemId) => {
setCartItems(cartItems.filter((ele)=>{
return ele.itemId !== itemId
}))
}
return (
<Router>
<Nav cartItems={cartItems} />
<Switch>
<Route exact={true} path="/">
<ItemListContainer handleAdd={addToCart} items={items} />
</Route>
<Route path="/shoppingcart">
<ShoppingCart
cartItems={cartItems}
items={items}
handleDelete={handleDelete}
handleQuantityChange={setQuantity} />
</Route>
</Switch>
</Router>
);
}
export default App;
ItemListContainer 페이지 구현
1. map 함수, props으로 전달받은 item 배열, item 컴포넌트를 사용하여 상품 목록 구현
2. map 함수의 두 번째 전달인자를 이용하여 item 컴포넌트의 key를 인덱스로 설정하기
3. 이벤트 핸들러와 handleAdd 메소드를 활용하여 app.js의 상태(state)를 변경하기
코드 작성
import React from 'react';
import Item from '../components/Item';
function ItemListContainer({ items, handleAdd }) {
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={() => {
handleAdd(item.id)
}} />)}
</div>
</div>
);
}
export default ItemListContainer;
ShoppingCart 페이지 구현
1. 객체 구조 분해 할당의 콜론 패턴을 사용하여 props를 전달 받기 (필수는 아님)
2. props로 받은 메소드를 다시 props로 전달할 때 : 새로운 함수를 만들어 props로 전달 그 안에서 메소드 실행
코드 작성
import React, { useState } from 'react'
import CartItem from '../components/CartItem'
import OrderSummary from '../components/OrderSummary'
// handleDelete는 onDelete로, handleQuantityChange는 onQuantityChange로 받기
export default function ShoppingCart({ items, cartItems, handleDelete: onDelete,
handleQuantityChange : onQuantityChange }) {
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([]);
}
};
// props로 받은 onQuantityChange를 함수 안에서 실행하기
const handleQuantityChange = (quantity, itemId) => {
onQuantityChange(itemId, quantity)
}
// props로 받은 onDelete를 함수 안에서 실행하기
const handleDelete = (itemId) => {
setCheckedItems(checkedItems.filter((el) => el !== itemId))
onDelete(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>
)
}
'프로그래밍 > 웹 개발' 카테고리의 다른 글
[node.js + React] 쿠키와 세션을 활용해 로그인 기능 구현 (0) | 2021.09.10 |
---|---|
[Node.js] Sequelize로 MVC 디자인 패턴 만들기 (0) | 2021.09.03 |
[React] Hook과 Styled Component를 통한 기능 구현 (0) | 2021.08.21 |
[Node.js] MySQL과 연동하여 서버 및 DB 구축하기 (0) | 2021.07.28 |
[Node.js] dontev 모듈과 .env를 사용하여 sql 비밀번호 숨기기 (0) | 2021.07.27 |
댓글