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>
);
}