React
- 장바구니에 상품을 추가하거나 삭제하고,
props를 활용하여 총 주문 금액의 상태값이 바뀌는 기능을 구현한다.
state.js
- state 파일에 임의의 값을 작성해 놓는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
export const initialState = {
"items": [
{
"id" : 1,
"name" : "노른자 분리기",
"img" : "../images/egg.png",
"price" : 9900
},
{
"id" : 2,
"name" : "2020년 달력",
"img" : "../images/2020.png",
"price" : 12000
},
{
"id" : 3,
"name" : "개구리 안대",
"img" : "../images/frog.png",
"price" : 2900
},
{
"id" : 4,
"name" : "뜯어온 보도블럭",
"img" : "../images/block.png",
"price" : 4900
},
{
"id" : 5,
"name" : "칼라 립스틱",
"img" : "../images/lip.png",
"price" : 2900
},
{
"id" : 6,
"name" : "잉어 슈즈",
"img" : "../images/fishegg.png",
"price" : 3900
},
{
"id" : 7,
"name" : "웰컴 매드",
"img" : "../images/welcome.png",
"price" : 6900
},
{
"id" : 8,
"name" : "강시 모자",
"img" : "../images/hat.png",
"price" : 9900
},
],
"cartItems":[
{
"itemId" : 1,
"quantity" : 1
},
{
"itemId" : 5,
"quantity" : 7
},
{
"itemId" : 2,
"quantity" : 3
},
]
}
App.js
- App 컴포넌트에서 상태값을 작성한다.
- 하나의 상태값을 여러 컴포넌트에서 공유하기 때문에 상위 컴포넌트에 작성한다.
- 임의로 작성한 기존값을 받아와,
initialState의items키가 가진 객체들을 상태값인items에 지정해준다. - 임의로 작성한 기존값을 받아와,
initialState의cartItems키가 가진 객체들을 상태값인cartItems에 지정해준다. - Router을 사용하여 Link에 따른 페이지를 보여준다.
- ItemListContainer 컴포넌트에
items,cartItems,setCartItems를 인자로 넘겨준다.- 상태값을 설정하는
useState자체를 인자로 넘겨줘서 사용할 수 있다.
- 상태값을 설정하는
- ShoppingCart 컴포넌트에도 마찬가지로 인자를 넘겨준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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
- Nav 컴포넌트는 Link 주솟값을 가진 상품리스트와 장바구니 메뉴를 갖고 있다.
- 장바구니 메뉴에는 우리가 장바구니에 담은 목록의 갯수만큼 숫자로 보여준다.
- App 컴포넌트에서
cartItems를 입력받아, 입력받은 객체의 갯수만큼 보여준다. cartItems는 state에서 불러온initialState.cartItems이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
ItemListContainer.js
- App 컴포넌트로부터
items (initialState.items),cartItems (initialState.cartItems),setCartItems를 전달받는다. - 전달받은
setCartItems로 App 컴포넌트의cartItems의 상태값을 변경할 수 있다. - Item 컴포넌트로 App 컴포넌트에서 전달받은
items와 작성된 함수handleClick을props로 넘겨준다. handleClick함수는cartItems에 추가될 새로운 배열newCartItem을 만들고,newCartItem의itemId키 값은 Item 컴포넌트에서 클릭 이벤트를 발생한 대상의id값이 들어간다.handleClick함수는 새롭게 작성할newCartItem의quantity키 값에 1을 할당하는데, 그 이유는for문을 돌려 만약 기존에cartItems안에 작성되어 있는id값과 비교하여 중복되는 경우 갯수인quantity를 증가시켜주고, 중복되지 않으면 장바구니에 담기지 않았다는 뜻이기에 클릭한 항목 자체를 추가해준다.setCartItems를 사용하여 새로운cartItems를 만들어주는 이유는, 리액트는 상태의 주솟값이 변하지 않고 요소들만 바뀔 시, 값이 바뀌지 않았다고 인식하기 때문에 주솟값 자체가 바뀐 새로운cartItems를 만들어 준다.return문이 없을 시, 이미 들어간 항목을 기존의cartItems배열과 함께 추가하여 작성하는setCartItems([...cartItems, newCartItem])까지 도달하기 때문에 중복된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React from "react";
import Item from "../components/Item";
function ItemListContainer({ items, cartItems, setCartItems }) {
const handleClick = (e, id) => {
let newCartItem = {};
newCartItem.itemId = id;
newCartItem.quantity = 1;
console.log(id);
for (let i = 0; i < cartItems.length; i++) {
if (cartItems[i].itemId === id) {
setCartItems([...cartItems]);
cartItems[i].quantity++;
return;
}
}
setCartItems([...cartItems, 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;
Item.js
- ItemListContainer 컴포넌트에게 전달받은
item,handleClick을 부여한다.item은 ItemListContainer에서 기존의 값인items를map으로 뿌려준 각각의 객체이다.button을 클릭 시, 클릭한 대상인e와 각각의items의 객체인item의id키 값을handleClick으로 다시 넘겨준다.handleClick함수의 결괏값이 어디로 가는지에 대해 생각하기보다 함수 자체가 리턴하는 것이 어느 것인지에 초점을 둔다.handleClick함수는 장바구니에 아이템이 겹치는지를 확인하여cartItems에 추가한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
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={() => handleClick(item.id)}>장바구니 담기</button>
</div>
)
}
ShoppingCart.js
- App 컴포넌트에서 ShoppingCart 컴포넌트로
items,cartItems,setCartItems를 넘겨준다. checkedItems상태는 입력받은cartItems의 각 객체의itemId를 갖고 있다.handleCheckChange,handleQuantityChange,handleDelete함수는 CartItem 컴포넌트에게props로 전달해준다.- CartItem 컴포넌트에게 전달된
handleCheckChange는input의checkbox를 클릭했을 때 발생하는 대상의checked여부와items의id를 넘겨준다.- 만약
checked가 된 상태라면cartItems의itemdId를map한 상태인checkedItems에 전달받은item.id(선택한 상품의 id)를 추가해준다. - 만약 선택한 체크 박스가 해제된 상태면
filter를 통해 선택한 상품의id가 일치하는 항목을checkedItems에서 빼준다.
- 만약
handleAllCheck함수는 ShoppingCart의input요소를 누르면 발생하며, 체크 박스가 선택된 상태라면cartItems의id를checkedItems상태에 모두 담아주며 체크 박스의 선택을 해제한 상태라면checkedItems를 모두 비워준다.handleQuantityChange를 CartItem 컴포넌트에게 전달하여 숫자를 조절하는input요소에 전달한다.- 이벤트를 발생시킨 요소의
value값인quantity와items를filter한item의id를 함수의 인자로 넘겨준다. - 만약
cartItems의 요소 중itemId키 값과 우리가 전달받은filter한item id가 일치하면, 일치하는 요소의quantity를 우리가input으로 입력한 숫자로 설정해준다. - 우리가 조작한 숫자로
quantity를 설정한다.
- 이벤트를 발생시킨 요소의
handleDelete또한 CartItem 컴포넌트에게 전달하여 선택한 요소의id를 전달인자로 받는다.- 전달인자로 입력받은
id를cartItems.filter를 통해 일치하는id를cartItems에서 제외한다. - 장바구니에서 삭제한다.
- 전달인자로 입력받은
getTotal함수는cartItems의itemsId를 가진 새로운 배열을 갖고 있는cartIdArr를 선언한다.checkedItems에cartIdArr가 있을 경우cartIdArr의 수량을total의quantity에 더해준다.- 총 수량 갯수 구하기
- 가격은
items를filter하여cartIdArr가 가르키는id와 일치하는 상품의 가격을 받아와total의price에 더해준다.- 총 주문 금액 구하기
renderItems함수는items를filter하여items의id와cartItems의 요소의id를 비교해서index값이 -1보다 크다는 것은 값이 있다는 말이기 때문에items의 요소를 남긴다.renderItems는 카트에 들어가 있는 요소 들이고, 이renderItems를 다시map으로 돌리는데 갯수는cartItems에서 일치하는id를 가진 상품의 갯수로 할당해준다.- 할당된
qauntity를 CartItem 컴포넌트에 넘겨준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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) => {
setCartItems(
cartItems.map((el) => {
if (el.itemId === itemId) {
el.quantity = quantity;
}
return el;
})
);
};
const handleDelete = (itemId) => {
console.log(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) {
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>
);
}