카테고리 없음
react정리6
jkkll
2022. 7. 23. 12:56
장바구니 context 추가
context 추가 위해 componenets옆에 store폴더 추가
cart-context.js파일 추가
import React from "react";
//아이템 배열, totalAmount초기값0, 추가아이템함수, 지우는함수
const CartContext = React.createContext({
items: [],
totalAmount: 0,
addItem: (item) => {},
removeItem: (id) => {},
});
export default CartContext;
CartProvider.js 파일 추가
import CartContext from "./cart-context";
//CartContext import후 provider에 접근
//cartContext도우미 상수를 이 컴포넌트함수에 추가
//단순히 items와 totalAmount로 된 객체
const CartProvider = (props) => {
const addItemToCartHandler = (item) => {};
const removeItemFromCartHandler = (id) => {};
//함수를 만들고 가리키도록 설정
const cartContext = {
items: [],
totalAmount: 0,
addItem: addItemToCartHandler,
removeItem: removeItemFromCartHandler,
};
return (
<CartContext.Provider value={cartContext}>
{props.children}
</CartContext.Provider>
);
};
export default CartProvider;
장바구니에 접근하는 모든 컴포넌트를 CartProvider로 감싸준다
App.js
import { Fragment, useState } from "react";
import Header from "./components/Layout/Header";
import Meals from "./components/Meals/Meals";
import Cart from "./components/Cart/Cart";
import CartProvider from "./store/CartProvider";
function App() {
const [cartIsShown, setCartIsShown] = useState(false);
const showCartHandler = () => {
setCartIsShown(true);
};
const hideCartHandler = () => {
setCartIsShown(false);
};
return (
<CartProvider>
{cartIsShown && <Cart onClose={hideCartHandler} />}
<Header onShowCart={showCartHandler} />
<main>
<Meals />
</main>
</CartProvider>
);
}
export default App;
Context사용
HeaderCartButton.js에서
useContext import
CartContext import
useContex호출해서 CartContext전달
상수 cartCtx에 저장
import { useContext } from "react";
import CartIcon from "../Cart/CartIcon";
import classes from "./HeaderCartButton.module.css";
import CartContext from "../../store/cart-context";
const HeaderCartButton = (props) => {
const cartCtx = useContext(CartContext);
//아이템갯수 목록 한개에 갯수만 추가하기 위해 reducer사용
//reduce는 내장 메소드 데이터배열을 값 하나로 변환해준다
//첫번째 인수는 함수, 두번째 인수는 시작값
//curNumber(현재수량) 사용자지정이름
//item 현재 보고있는 항목
//현재수량에 아이템의 숫자를 더한다
//그 값이 리턴되고..
//밑에 리턴에서 적용시킨다 함수이름을
const numberOfCartItems = cartCtx.items.length.reduce((curNumber, item) => {
return curNumber + item.amount;
}, 0);
return (
<button className={classes.button} onClick={props.onClick}>
<span className={classes.icon}>
<CartIcon />
</span>
<span>Your Cart</span>
<span className={classes.badge}>{numberOfCartItems}</span>
</button>
);
};
export default HeaderCartButton;
장바구니 항목 추가
CartProvider.js로 이동
import CartContext from "./cart-context";
import { useReducer } from "react";
//리듀서 함수에서 리턴할 새 state 스냅샷을 위해 리듀서 밖에 함수 생성
//그리고 이 함수를 cartReducer에서 반환
//cartProvider에서 useReducer호출
const defaultCartState = {
items: [],
totalAmount: 0,
};
//state는 최신 state스냅샷
//장바구니 항목을 추가하기 위한 추가로직 설정
//어떤 타입인지 확인 위해 if 타입체크
const cartReducer = (state, action) => {
//타입이 ADD일 경우 여기에서 장바구니 항목 업데이트
//같은 항목은 같이 그룹화해서 수량관리, total값도 업데이트
//concat은 자바스크립트 메소드로 기존 배열을 편집하는 게 아니라 새 배열을 반환
//얻게될 action.item을 concat한다
//업데이트된 토탈 가격 이전 state스냅샷의 totalAmount에 price, amount 곱한것들 더한다
//얻게될 item에 amount필드와 price필드가 있을 것으로 예상
//새로운 state반환
if (action.type === "ADD") {
const updatedItems = state.items.concat(action.item);
const updatedTotalAmount =
state.totalAmount + action.item.price * action.item.amount;
return {
items: updatedItems,
totalAmount: updatedTotalAmount,
};
}
return defaultCartState;
};
//CartContext import후 provider에 접근
//cartContext도우미 상수를 이 컴포넌트함수에 추가
//단순히 items와 totalAmount로 된 객체
const CartProvider = (props) => {
//()의 첫번째 인수로 리듀서 함수 가리키고, 초기 state설정하는데 만들어둔 상수 재사용
//[]배열 첫번째 요소는 항상 state스냅샷 이름은 사용자설정, 두번째요소는 함수 이걸 통해 리듀서에 액션전달 사용자설정이름
const [cartState, dispatchCartAction] = useReducer(
cartReducer,
defaultCartState
);
//이 함수가 호출될 때마다 장바구니 추가할 항목을 얻는다
//장바구니에 같은 항목이 있는지 확인하고, 있다면 갯수 업데이트
//없다면 새 항목을 추가하도록 만든다
//state관리를 위해 useState나 useReducer를 사용하는데 여기서 사용시 복잡하기 때문에 reducer사용
//useReducer설정 후 장바구니 액션을 정의할 수 있는데
//useReducer의 액션을 정의하는 것은 전적으로 사용자에게 달려있다
//숫자나 텍스트일 수도 있지만 일반적으로 객체
//어떤 속성을 가지고 있고 이를 통해 해당 액션을 식별한다
//속성추가 후 두번째 속성에 항목전달 item이라고 속성이름 정하고 item을 가리키도록 설정
const addItemToCartHandler = (item) => {
dispatchCartAction({ type: "ADD", item: item });
};
//액션타입이 2가지가 됨 위에 add와 remove
const removeItemFromCartHandler = (id) => {
dispatchCartAction({ type: "REMOVE", id: id });
};
//함수를 만들고 가리키도록 설정
//reducer함수 다 설정한 후 items에서 빈 배열 대신 cartState.items에 접근하도록 설정
//item은 state로 cartState.totalAmount로 관리
const cartContext = {
items: cartState.items,
totalAmount: cartState.totalAmount,
addItem: addItemToCartHandler,
removeItem: removeItemFromCartHandler,
};
return (
<CartContext.Provider value={cartContext}>
{props.children}
</CartContext.Provider>
);
};
export default CartProvider;
장바구니 항목 출력
Cart.js
//모든 장바구니 아이템 랜더링, 총 수량 표시
import { useContext } from "react";
import Modal from "../UI/Modal";
import CartItem from "./CartItem";
import classes from "./Cart.module.css";
import CartContext from "../../store/cart-context";
//cartCtx.items에 접근
const Cart = (props) => {
const cartCtx = useContext(CartContext);
//toFixed 소수점2자리까지 표시
const totalAmount = `$${cartCtx.totalAmount.toFixed(2)}`;
const hasItems = cartCtx.items.length > 0;
//제거함수 삭제할 항목의 id를 얻음
const cartItemRemoveHandler = (id) => {};
//항목 자체를 가져올 함수
const cartItemAddHandler = (item) => {};
//카트에 표시되는 아이템 설정위해 컴포넌트 따로만들어진 거에 key값추가
//bind(null, item.id) 추가되거나 삭제된 항목의 id가 remove헨들러로 전달된다
const cartItems = (
<ul className={classes["cart-items"]}>
{cartCtx.items.map((item) => (
<CartItem
key={item.id}
name={item.name}
amount={item.amount}
price={item.price}
onRemove={cartItemRemoveHandler.bind(null, item.id)}
onAdd={cartItemAddHandler.bind(null, item.id)}
/>
))}
</ul>
);
return (
<Modal onClose={props.onClose}>
{cartItems}
<div className={classes.total}>
<spam>Total Amount</spam>
<span>{totalAmount}</span>
</div>
<div className={classes.actions}>
<button className={classes["button--alt"]} onClick={props.onClose}>
Close
</button>
{hasItems && <button className={classes.button}>Order</button>}
</div>
</Modal>
);
};
export default Cart;
장바구니 안에서도 항목을 추가 및 삭제하기
import CartContext from "./cart-context";
import { useReducer } from "react";
const defaultCartState = {
items: [],
totalAmount: 0,
};
const cartReducer = (state, action) => {
if (action.type === "ADD") {
const updatedTotalAmount =
state.totalAmount + action.item.price * action.item.amount;
//findeIndex() 배열에서 항목의 인덱스를 찾아준다
//item.id가 action.item.id와 같은 경우 true반환
//현재배열에서 보고있는 항목이 전달된 액션으로 추가하는 항목과 동일한 id를 가지는 경우
//해당 항목이 존재한다면 해당항목의 인덱스를 돌려준다
const existingCartItemIndex = state.items.findIndex(
(item) => item.id === action.item.id
);
const existingCartItem = state.items[existingCartItemIndex];
let updatedItems;
//카트에 아이템이 있을경우 항목복사 수량 업데이트
//액션에 의해 추가된 수량을 더해준다
if (existingCartItem) {
const updatedItem = {
...existingCartItem,
amount: existingCartItem.amount + action.item.amount,
};
updatedItems = [...state.items];
updatedItems[existingCartItemIndex] = updatedItem;
} else {
updatedItems = state.items.concat(action.item);
}
return {
items: updatedItems,
totalAmount: updatedTotalAmount,
};
}
//action 타입이 remove일 경우의 타입체크
//장바구니에서 -버튼 누르면 제거하기
if (action.type === "REMOVE") {
const existingCartItemIndex = state.items.findIndex(
(item) => item.id === action.id
);
const existingItem = state.items[existingCartItemIndex];
const updatedTotalAmount = state.totalAmount - existingItem.price;
let updatedItems;
//수량이 1개일 경우 버튼클릭시 항목까지 삭제
//특정 id를 가진 항목 삭제
//else경우 항목을 그대로 두고 수량만 줄인다
if (existingItem.amount === 1) {
updatedItems = state.items.filter((item) => item.id !== action.id);
} else {
//수량 업데이트 -로
const updatedItem = { ...existingItem, amount: existingItem.amount - 1 };
updatedItems = [...state.items];
updatedItems[existingCartItemIndex] = updatedItem;
}
return {
items: updatedItems,
totalAmount: updatedTotalAmount,
};
}
return defaultCartState;
};
//CartContext import후 provider에 접근
//cartContext도우미 상수를 이 컴포넌트함수에 추가
//단순히 items와 totalAmount로 된 객체
const CartProvider = (props) => {
//()의 첫번째 인수로 리듀서 함수 가리키고, 초기 state설정하는데 만들어둔 상수 재사용
//[]배열 첫번째 요소는 항상 state스냅샷 이름은 사용자설정, 두번째요소는 함수 이걸 통해 리듀서에 액션전달 사용자설정이름
const [cartState, dispatchCartAction] = useReducer(
cartReducer,
defaultCartState
);
const addItemToCartHandler = (item) => {
dispatchCartAction({ type: "ADD", item: item });
};
//액션타입 2가지 add와 remove
const removeItemFromCartHandler = (id) => {
dispatchCartAction({ type: "REMOVE", id: id });
};
//함수를 만들고 가리키도록 설정
//reducer함수 다 설정한 후 items에서 빈 배열 대신 cartState.items에 접근하도록 설정
//item은 state로 cartState.totalAmount로 관리하기 때문에 저렇게 적어줌
const cartContext = {
items: cartState.items,
totalAmount: cartState.totalAmount,
addItem: addItemToCartHandler,
removeItem: removeItemFromCartHandler,
};
return (
<CartContext.Provider value={cartContext}>
{props.children}
</CartContext.Provider>
);
};
export default CartProvider;
useEffect사용
import { useContext, useEffect, useState } from "react";
import CartIcon from "../Cart/CartIcon";
import classes from "./HeaderCartButton.module.css";
import CartContext from "../../store/cart-context";
const HeaderCartButton = (props) => {
//버튼클릭시 애니메이션 효과 실행 useEffect, useState사용
const [btnIsHiglighted, setBtnIsHighlighted] = useState(false);
const cartCtx = useContext(CartContext);
//객체로 지정해준다
const { items } = cartCtx;
const numberOfCartItems = items.reduce((curNumber, item) => {
return curNumber + item.amount;
}, 0);
//클래스들을 const로 묶어준다 버튼에 적용
const btnClasses = `${classes.button} ${btnIsHiglighted ? classes.bump : ""}`;
useEffect(() => {
if (items.length === 0) {
return;
}
setBtnIsHighlighted(true);
//클래스가 추가된 상태에서 한 번만 실행되기 때문에
//클래스를 삭제해야한다
//300ms타이머설정(애니메이션 재생속도 CSS에서 설정한)
//setBtnIsHighlighted가 false가 되면 btnClasses에서 빈 문자열이 된다
//300ms후에 클래스가 삭제된다(애니메이션이 재생되는 속도)
const timer = setTimeout(() => {
setBtnIsHighlighted(false);
}, 300);
//clean up function 위해 timer라는 상수부분 추가 const timer
return () => {
clearTimeout(timer);
};
}, [items]);
return (
<button className={btnClasses} onClick={props.onClick}>
<span className={classes.icon}>
<CartIcon />
</span>
<span>Your Cart</span>
<span className={classes.badge}>{numberOfCartItems}</span>
</button>
);
};
export default HeaderCartButton;