다음 시간에 배울 Redux의 필요성을 위해 props로 부모 컴포넌트에서 상태를 여러번 내려지는 상황의 과제를 했다. Redux는 상태 관리를 위한 여러 툴 중 하나로 컴포넌트의 수가 많아질수록 한 번에 어떤 상태를 주기 힘든 상황이 되는데 이를 좀 더 수월하게 해주는 상태 관리 툴이다.
코드의 양이 길어질 수록, 그리고 이번 과제에서 props로 여러 값들을 내려줘야 하기 때문에 컴포넌트들의 구조를 파악하는게 중요했다. 보통은 리엑트 파일을 작성하게 된다면 App.js파일이 제일 첫번째 컴포넌트로 사용되기 때문에 이 부분부터 구조를 파악한다. 한번에 이해하기 힘들다면 다이어그램
을 이용하거나
구글 익스텐션 중 React developer tools
를 이용해 컴포넌트의 구조를 쉽게 파악할 수 있다.
React developer tools
을 다운받고 리엑트 파일을 start한 뒤 콘솔창에서 확인 가능하다.function App() {
const [items, setItems] = useState(initialState.items);
const [cartItems, setCartItems] = useState(initialState.cartItems);
return (
<Router>
<Nav cartItems={cartItems} /> // 장바구니에 들어간 물건의 갯수를 작성하기 위해 cartItems를 props로 내려준다.
<Routes>
<Route
path="/"
element={
<ItemListContainer
items={items}
cartItems={cartItems}
setCartItems={setCartItems}
/>
// ItemListContainer의 props로 items, cartItems, setCartItems을 내려준다.
}
/>
<Route
path="/shoppingcart"
element={
<ShoppingCart
items={items}
cartItems={cartItems}
setCartItems={setCartItems}
/>
// ShoppingCart의 props로 items, cartItems, setCartItems을 내려준다.
}
/>
</Routes>
<img
id="logo_foot"
src={`${process.env.PUBLIC_URL}/codestates-logo.png`}
alt="logo_foot"
/>
</Router>
);
}
데이터로 쓰인 initialState에는 2개의 key값이 있는데 items에는 자료들이 들어있고 cartItems에는 장바구니에 넣은 자료들의 id값과 몇개를 넣을건지 갯수가 들어있다. 두개의 키 값 각각 state에 저장 되어있다.
function Nav({ cartItems }) { // props로 받은 데이터를 인자로 받는다.
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>
// 카트에 몇개의 물건을 담았는지 숫자로 나타내면 되기 때문에 .length를 써서 숫자를 보이게 했다.
</Link>
</div>
</div>
);
}
물건담기 버튼을 눌렀을때 장바구니로 물건이 들어가는것은 cartItems에 데이터가 추가된다는 것이기 때문에 cartItems를 props로 내려줬다.
그 후 버튼이 들어있는 Item
컴포넌트로 들어간다.
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={(e) => handleClick(e, item.id)}>장바구니 담기</button>
</div>
)
}
인자로 item, handleClick()를 받고있다. item과 handleClick()를 확인하기 위해 부모컴포넌트인 ItemListContainer
로 간다.
function ItemListContainer({ items, cartItems, setCartItems }) {
const handleClick = (itemId) => {
if (!cartItems.find((el) => el.itemId === itemId)) {
setCartItems([
...cartItems,
{
itemId: itemId,
quantity: 1,
},
]);
}
};
// 상품담기 버튼을 클릭했을때 실행시키는 함수로 인자로 클릭했을때 그 물건의 id 값을 인자로 받는다.
// 만약 cartItems의 itemId값과 클릭해서 얻은 itemId값이 같지 않을때 cartItems에 클릭한 itemId의 값을 가지는 데이터를 추가해준다.
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} />
))}
// `Item`컴포넌트의 인자로받은 item은 부모컴포넌트로부터 받은 items의 요소들임을 알 수 있다.(map함수의 인자로 받은 값을 보고 확인 가능)
// 위와 비슷하게 Item컴포넌트는 handleClick()을 내려주고 있는데 ItemListContainer컴포넌트 내부의 함수를 내려주는것을 확인할 수 있다.
</div>
</div>
);
}
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
type="number"
min={1}
className="cart-item-quantity"
value={quantity}
onChange={(e) => {
handleQuantityChange(Number(e.target.value), item.id)
}}>
</input>
<button className="cart-item-delete" onClick={() => { handleDelete(item.id) }}>삭제</button>
</li >
)
}
갯수 수정과 삭제버튼이 있는 CartItem컴포넌트로 가서 확인해보면 handleQuantityChange(), handleDelete()는 부모 컴포넌트로부터 받아온것을 알 수 있다.
const handleQuantityChange = (quantity, itemId) => {
// 첫번째 인자로 `quantity`가 들어오는데 이는 `event.target.value` 값으로 물건의 갯수가 변화하는 값으로 사용되고 있다.
setCartItems(
cartItems.map((el) => {
if (el.itemId === itemId) {
return {
itemId: itemId,
quantity: quantity,
};
// cartItems의 itemId값과 누른 itemId값이 같을때 quantity값을 quantity바꿔준다.
} else {
return el;
}
// 값이 같지 않으면 cartItems 요소를 그대로 리턴 해준다.
})
);
};
const handleDelete = (itemId) => {
setCheckedItems(checkedItems.filter((el) => el !== itemId));
setCartItems(cartItems.filter((el) => el.itemId !== itemId));
//
};
장바구니의 삭제기능을 구현하는것은 cartItems내부에서 같은 값을 빼준다는 것이기 때문에 itemId값과 같지 않은것만 리턴해주면 삭제하고싶은 데이터를 제외하고 나머지를 리턴 해주기 때문에 원하는 값만 삭제 가능하다.