Cmarket (Hooks 버전)
Hooks를 이용해 상태를 다루는 것이 목적입니다. 또한 컴포넌트끼리 상태를 주고받는 과정을 연습할 수 있습니다.
TODO: 장바구니에 추가 및 상품 개수 업데이트
다음과 같이 작동해야 합니다.
- 메인 화면에서 [장바구니 담기] 버튼을 누른 후, 장바구니 페이지로 이동하면 상품이 담겨있어야 합니다.
- 내비게이션 바에 상품 개수가 즉시 표시되어야 합니다.
App.js
import React, { useState } from "react";
import Nav from "./components/Nav";
import ItemListContainer from "./pages/ItemListContainer";
import "./App.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 itemsLength={cartItems.length} />
<Routes>
<Route
path="/"
element={
<ItemListContainer items={items} setCartItems={setCartItems} />
}
/>
<Route
path="/shoppingcart"
element={
<ShoppingCart
cartItems={cartItems}
items={items}
setCartItems={setCartItems}
/>
}
/>
</Routes>
</Router>
);
}
export default App;
itemListContainer.js
import React from "react";
import Item from "../components/Item";
function ItemListContainer({ items, setCartItems }) {
const handleClick = (e, id) => {
const newCartItem = {
itemId: id,
quantity: 1,
};
//prev에 가져온 데이터 추가하기
setCartItems((prev) => [...prev, newCartItem]);
};
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;
TODO: 장바구니로부터 제거
- 장바구니 페이지에서 [삭제] 버튼을 누른 후, 해당 상품이 목록에서 삭제되어야 합니다.
- 내비게이션 바에 상품 개수가 즉시 표시되어야 합니다.
Shoppingcart.js
import React, { useEffect, 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 [total, setTotal] = useState({});
// useEffect 실행조건 넣을 수 있는 곳은 []
// useEffect(()=>{ 실행할코드 }, [count])
//cartItems 변수가 변할 때만 useEffect 안의 코드가 실행됨.
useEffect(() => {
setTotal(getTotal());
}, [cartItems]);
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) => {
// quantity: e.target.value(아이템 개수), itemId : item.id
// cartItems => item.id 의 quantity 변경해주기
let targetItemidx;
cartItems.forEach((el, idx) =>
el.itemId === itemId ? (targetItemidx = idx) : null
);
setCartItems(
cartItems.map((el, idx) =>
idx === targetItemidx ? { itemId: el.itemId, quantity: quantity } : el
)
);
//cartItems[targetItemidx].quantity = quantity;
};
const handleDelete = (itemId) => {
setCheckedItems(checkedItems.filter((el) => el !== itemId));
setCartItems(cartItems.filter((el) => el.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) {
//checkedItems에 cartIdArr 요소가 있는지 판별
let quantity = cartItems[i].quantity;
let price = items.filter((el) => el.id === cartItems[i].itemId)[0]
.price;
total.quantity = total.quantity + quantity;
total.price = total.price + quantity * price;
}
}
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>
);
}
cartItem.js
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 lue={quantity}
onChange={(e) => {
handleQuantityChange(Number(e.target.value), item.id)
}}>
</input>
<button className="cart-item-delete" onClick={() => { handleDelete(item.id) }}>삭제</button>
</li >
)
}
React에서 Lifecycle hook 쓰는 법
useEffect import해오고 콜백함수 추가해서 안에 코드 적으면 이제 그 코드는 컴포넌트가 mount & update시 실행된다.
그래서 Lifecycle hook 라고 한다.
컴포넌트의 핵심 기능은 html 렌더링이다. 조금이라도 html 렌더링이 빠른 사이트를 원하면 오래걸리는 반복연산, 서버에서 데이터가져
오는 작업, 타이머 다는것 등 기능들은 useEffect 안에 넣는다. useEffect 안에 적은 코드는 html 렌더링 이후에 동작하기 때문이다.
useEffect에 넣을 수 있는 실행조건
useEffect(
()=>{ 실행할코드 }, [count])
useEffect()의 둘째 파라미터로 [ ] 를 넣을 수 있는데 변수나 state같은 것들을 넣을 수 있다.
그렇게 하면 [ ]에 있는 변수나 state 가 변할 때만 useEffect 안의 코드를 실행한다.
그래서 위의 코드는 count라는 변수가 변할 때만 useEffect 안의 코드가 실행된다.
(참고) [ ] 안에 state 여러개 넣을 수 있다.
useEffect(
()=>
{ 실행할코드 }, [])
아무것도 안넣으면 컴포넌트 mount시 (로드시) 1회 실행하고 영영 실행해주지 않는다.
useEffect(()=>{
2. 그 다음 실행됨
return ()=>{
1. 여기있는게 먼저실행됨
}
}, [count])
useEffect 동작하기 전에 특정코드를 실행하고 싶으면 return ()=>{} 안에 넣을 수 있다.
useEffect 안에 있는 코드를 실행하기 전에 return ()=>{ } 안에 있는 코드를 실행해준다. 이것을 clean up function 이라고 칭한다.
useEffect(()=>{
let a = setTimeout(()=>{ setAlert(false) }, 2000)
return ()=>{
clearTimeout(a)
}
}, [])
setTimeout() 쓸 때마다 브라우저 안에 타이머가 하나 생긴다. useEffect 안에 썼기 때문에 컴포넌트가 mount 될 때 마다 실행되고
잘못 코드를 짜면 타이머가 100개 1000개 생길 수도 있다. 버그를 방지하고 싶으면useEffect에서 타이머 만들기 전에 기존 타이머를
제거되도록 만든다. 타이머 제거하고 싶으면 return ()=>{} 안에 clearTimeout(타이머) 이렇게 코드짜면 된다.
'React' 카테고리의 다른 글
React Hooks (0) | 2022.07.08 |
---|---|
React 상태관리 (0) | 2022.07.07 |
[React] CDD 개발 도구 Styled Components Props 활용 (0) | 2022.07.01 |
[React] CDD 개발 도구 Styled Components (0) | 2022.07.01 |
[React] 컴포넌트 주도 개발 Component Driven Development (CDD) (0) | 2022.06.30 |