요즘에 과제를 하면서 점점 코드가 길어지니까 어지러워지는 느낌이예요.
특히 코드가 길고, 파일이 여러개 있을때는
전체적인 구조를 파악하는 것이 중요한데요.
오늘 과제는 그림도 그려보면서 전체적인 구조 파악에 중점을 둬서 보도록 하겠습니다
구조파악 3단계
1. 컴포넌트 기준으로 tree 구조 작성
2. state가 어디서 정의되고, 내려오고 있는지
3. 함수가 어디서 정의되고, 내려오고 있는지
1,2,3 번을 보여주는 간단한 그림을 그려봤습니다
컴포넌트만 속속 골라서 부모 자식관계에 따라 tree 구조도를 그려보았습니다. (크롬 익스텐션 사용하는 것도 좋음)
자식컴포넌트 어떻게 찾냐구요?
리턴문에 가서 찾는다!
App 이라는 부모 컴포넌트에서 자식 컴포넌트인 ItemListContainer, Shopping Cart, Nav 로 상태가 흐르는 것을 볼 수 있습니다.
구조를 어느정도 잡으면, 시작하면 됩니다
하지만, 무작정 시작하면 안되겠죠. 순차적인 단계를 거쳐야 합니다
1. 필요한 기능을 명료하게 정리
처음에는 너무 자세히 적지 않아도 됩니다. 적당히 어떤 기능이 있는지 그림이나 화살표로 표현할 수 있는 정도면 됩니다.
세세한 사항은 코드를 작성하면서 수정해도 좋습니다
(1) 리스트에서 장바구니에 상품 추가
(2) 장바구니에서 상품 삭제
(3) 장바구니에서 수량 변경
(4) 장바구니 상단 바에 있는 숫자 변경
이번엔 4가지 기능을 구현할겁니다
2. 어떤 데이터 형태를 사용하는지 파악하기
항상, 받아오는 데이터 형태를 살펴본다음, 나중에 변경을 해야한다면 어떤 식으로 변경해야할지 생각해볼 수 있음
- 상품 목록 (items): 개별 상품이 배열의 형태로 담겨 있습니다.
{
"id": 1,
"name": "노른자 분리기",
"img": "../images/egg.png",
"price": 9900
}
- 장바구니 목록(cartItems): 장바구니에는 상품 아이디와, 수량을 담은 객체가 배열의 형태로 담겨 있습니다.
{
"itemId": 1,
"quantity": 1
}
3. 부모에서 어떤 props를 내려줘야 하는지 정리해보기
부모 컴포넌트에 props를 정의해주고, 자식 컴포넌트에서 props를 받아옵니다
다음은 부모컴포넌트에 어떤 props 를 정의하고, 받아와야 하는지 나타낸 것입니다
(1) 리스트에서 장바구니에 상품 추가
- cartItems 사용
- setCartItems 로 변경
(2) 장바구니에서 상품 삭제
- cartItems 사용
- setCartItems 로 변경
(3) 장바구니에서 수량 변경
- cartItems 사용
- setCartItems 로 변경
(4) 장바구니 상단 바에 있는 숫자 변경
- nav에서 cartItems의 데이터가 필요하겠다
- cartItems 사용
(App.js)
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>
// 각각의 컴포넌트에다가 props(cartItems={cartItems} setCartItems={setCartItems})를 등록해주었죠.
<Route path="/" element={<ItemListContainer items={items} cartItems={cartItems} setCartItems={setCartItems}/>} />
<Route
path="/shoppingcart"
element={<ShoppingCart items={items} cartItems={cartItems} setCartItems={setCartItems}/>}
/>
</Routes>
<img
id="logo_foot"
src={`${process.env.PUBLIC_URL}/codestates-logo.png`}
alt="logo_foot"
/>
</Router>
);
}
export default App;
4. 기능 확인하면서 코드 작성하기
(1) 리스트에서 장바구니에 상품 추가
handleClick
로직: 장바구니 담기 버튼을 클릭했을때,
지금 장바구니에 추가할 상품이 이미 있으면 수량 +1,
없으면 새롭게 상품 리스트에 추가
findIndex 메소드, 배열에서 특정 요소를 찾음
import React from 'react';
import Item from '../components/Item';
function ItemListContainer({ items, cartItems, setCartItems }) {
const handleClick = (e, id) => { //클릭을 할때, 이벤트 객체와 누른 상품에 해당하는 id가 들어오는것
// 각 파라미터: 이벤트 객체, 고유식별자
// 장바구니에 없는 아이템은 추가해주고
// 있는 아이템이면 수량만 +1
//findIndex를 통해 인덱스 숫자를 찾았으니, 그걸 대입해줌
let findNumber =cartItems.findIndex((el)=> el.itemId === id)
if(findNumber === -1){
let newcartItems = [...cartItems]
newcartItems.push({"itemId": id,"quantity": 1})
setCartItems(newcartItems)
}else{
let newcartItems = [...cartItems]
newcartItems[findNumber].itemId += 1
setCartItems(newcartItems)
}
}
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;
(2) 장바구니에서 상품 삭제
HandleDelete
로직: 장바구니에서 받아온 파라미터 id 와 동일한 상품이 있다면
제외하고 새로운 배열을 만들기
filter 메소드, 특정 요소만 남김
(3) 장바구니에서 수량 변경
handleQuantityChange
로직: 입력된 파라미터 id 같은 것을
골라서 그것의 quantity를 +1 해준다
findIndex 메소드, 배열에서 특정 요소를 찾음
+map 함수의 바꿔치기 기능을 써도됨 !
(특정 속성만 살짝 바꾸고 싶을때, 반드시 return을 작성해주어야 함)
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) => {
//cartitems 들 중에서 입력된 itemId와 같은 것을 골라서 그것의 quantity를 변경해준다
// 참고로, quantity도 입력값이기 때문에 재할당을 해주면 된다
let findIdx = cartItems.findIndex((el)=>el.itemId === itemId)
let newCartItems = [...cartItems]
newCartItems[findIdx].quantity = quantity
setCartItems(newCartItems)
}
const handleDelete = (itemId) => {
setCartItems(cartItems.filter((el) => el.itemId !== itemId))
}
//CartItems 중에서 입력된 파라미터와 다른 itemId를 가진 items들끼리 새로운 배열만들기
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>
)
}
(4) 장바구니 상단 바에 있는 숫자 변경
props로 {cartItems}를 받아오고
따로 state를 만들지 않고 cartItems.length 를 사용하기
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;
헉헉,,, 이렇게 계속 위에서 state가 내려오면 방식으로 코드를 쓰니
불필요하게 중간 컴포넌트에서 계속 전달을 해줘야 하죠?
이런 부분을 다음주 리덕스를 배우면서 해결할 수 있다고 합니다!
'React' 카테고리의 다른 글
Redux란? ('store', 'reducer', 'action' ) (0) | 2023.04.24 |
---|---|
React Custom Component 만들기 (자동완성, autocomplete) (0) | 2023.04.22 |
props와 state의 차이점 (0) | 2023.04.21 |
React Custom Component 만들기 (모달, 토글, 탭) (0) | 2023.04.19 |
Styled Components 문법 총정리 (0) | 2023.04.18 |