오늘은 이전에 useState를 사용하여 구현했던 간단한 장바구니 기능이 구현된 웹페이지를 Redux를 사용하여 데이터를 관리할 수 있도록 수정하는 스프린트를 구현하였다. 이론으로만 학습했을때 이해되지 않았던 것들이 실제로 코드를 작성하면서 어떤 방식으로 State가 업데이트되고 왜 이러한 구조로 되어 있는지 알 수 있었다.
import React, { useState } from 'react'; import Nav from './components/Nav'; import ItemListContainer from './pages/ItemListContainer'; import NotificationCenter from './components/NotificationCenter'; import './App.css'; import { BrowserRouter as Router, Switch, Route, } from "react-router-dom"; import ShoppingCart from './pages/ShoppingCart'; function App() { return ( <Router> <Nav /> <Switch> <Route exact={true} path="/"> <ItemListContainer /> </Route> <Route path="/shoppingcart"> <ShoppingCart /> </Route> </Switch> <NotificationCenter /> </Router> ); } export default App;
import React from 'react'; import { addToCart, notify } from '../actions/index'; import { useSelector, useDispatch } from 'react-redux'; import Item from '../components/Item'; function ItemListContainer() { const state = useSelector(state => state.itemReducer); const { items, cartItems } = state; const dispatch = useDispatch(); const handleClick = (item) => { if (!cartItems.map((el) => el.itemId).includes(item.id)) { dispatch(addToCart(item.id)) dispatch(notify(`장바구니에 ${item.name}이(가) 추가되었습니다.`)) } else { dispatch(notify('이미 추가된 상품입니다.')) } } 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) }} />)} </div> </div> ); } export default ItemListContainer;
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" data-testid={item.name}>{item.name}</span> <span className="item-price">{item.price}</span> <button className="item-button" onClick={(e) => handleClick(e, item.id)}>장바구니 담기</button> </div> ) }
import React, { useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { removeFromCart, setQuantity } from '../actions'; import CartItem from '../components/CartItem' import OrderSummary from '../components/OrderSummary' export default function ShoppingCart() { const state = useSelector(state => state.itemReducer); const { cartItems, items } = state const dispatch = useDispatch(); 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) => { dispatch(setQuantity(itemId, quantity)) } const handleDelete = (itemId) => { setCheckedItems(checkedItems.filter((el) => el !== itemId)) dispatch(removeFromCart(itemId)) //TODO: dispatch 함수를 호출하여 액션을 전달하세요. } 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> ) }
export const ADD_TO_CART = "ADD_TO_CART"; export const REMOVE_FROM_CART = "REMOVE_FROM_CART"; export const SET_QUANTITY = "SET_QUANTITY"; export const NOTIFY = "NOTIFY"; export const ENQUEUE_NOTIFICATION = "ENQUEUE_NOTIFICATION"; export const DEQUEUE_NOTIFICATION = "DEQUEUE_NOTIFICATION"; // actions creator functions export const addToCart = (itemId) => { return { type: ADD_TO_CART, payload: { quantity: 1, itemId } } } export const removeFromCart = (itemId) => { return { type: REMOVE_FROM_CART, payload: { itemId } } } export const setQuantity = (itemId, quantity) => { return { type: SET_QUANTITY, payload: { quantity, itemId } } } export const notify = (message, dismissTime = 5000) => dispatch => { const uuid = Math.random() dispatch(enqueueNotification(message, dismissTime, uuid)) setTimeout(() => { dispatch(dequeueNotification()) }, dismissTime) } export const enqueueNotification = (message, dismissTime, uuid) => { return { type: ENQUEUE_NOTIFICATION, payload: { message, dismissTime, uuid } } } export const dequeueNotification = () => { return { type: DEQUEUE_NOTIFICATION } }
import React from 'react'; import { addToCart, notify } from '../actions/index'; import { useSelector, useDispatch } from 'react-redux'; import Item from '../components/Item'; function ItemListContainer() { const state = useSelector(state => state.itemReducer); const { items, cartItems } = state; const dispatch = useDispatch(); const handleClick = (item) => { if (!cartItems.map((el) => el.itemId).includes(item.id)) { dispatch(addToCart(item.id)) dispatch(notify(`장바구니에 ${item.name}이(가) 추가되었습니다.`)) } else { dispatch(notify('이미 추가된 상품입니다.')) } } 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) }} />)} </div> </div> ); } export default ItemListContainer;
내일은 HA2가 진행될 예정이고 코플릿 3문제를 오후에 풀고, 그 다음날 과제형 스프린트를 제출하는 형태로 진행된다. 주말 동안 코드 스테이츠 측에서 보여준 test 관련 README를 참고하여 실제로 웹페이지를 구현하였다. React를 사용한 웹페이지 외에 서버와 클라이언트까지 구현할 수 있어서 HA를 대비하는데 도움이 되었다.