React Shopping Tutorial

Coaspe·2021년 1월 28일
0

React_Practice

목록 보기
2/2

> > React Shopping Tutorial

위의 영상을 복습하는 글이다.

index.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {QueryClient, QueryClientProvider} from 'react-query';

const client = new QueryClient();

ReactDOM.render( 
    <QueryClientProvider client={client}>
        <App />
    </QueryClientProvider>
,document.getElementById('root'));

App.tsx

import {useState} from 'react'
import {useQuery} from 'react-query'

import Item from './Item/Item'; 
* 받아온 데이터에 있는 하나의 상품 Component
import Grid from '@material-ui/core/Grid'; 
* 컨텐츠를 반응형 격자로 배치시켜야 하는 경우 유용합니다. 
* Material UI는 기본적으로 12격자(grid) 시스템을 가지고 있으며,브레이크포인트(breakpoint) 별로 각 셀이 몇 열을 차지할 것인지를 명시해줄 수 있습니다.

import AddShoppingCartIcon from '@material-ui/icons/AddShoppingCart'; 
* Side Navigation을 열어줄 카트모양의 아이콘

import Badge from '@material-ui/core/Badge'; 
* Cart에 담긴 수량을 알려주는 Badge

import {Wrapper, StyledButton} from './App.styles';

export type CartItemType = { 
 id: number;
 category: string;
 description: string;
 image: string;
 price: number;
 title: string;
 amount: number;
}
* Typescript의 type 선언 CartItemType형 데이터는 아래의 구성요소를 갖고 있어야 한다.

const getProducts = async (): Promise<CartItemType[]> =>  
 await(await >fetch('https://fakestoreapi.com/products')).json();
* 비동기화 함수인 async를 이용해서 API로 부터 데이터를 받아온다.
* await가 두개인 이유는 json() 때문이다.
* Promise는 프로미스가 생성될 때 꼭 알 수 있지는 않은 값을 위한 대리자로, 
 비동기 연산이 종료된 이후의 결과값이나 실패 이유를 처리하기 위한 처리기를 연결할 수 있도록 한다.
 프로미스를 사용하면 비동기 메서드에서 마치 동기 메서드처럼 값을 반환할 수 있다.

const App = () => {
 const [cartOpen, setCartOpen] = useState(false);
  * Cart가 열려있는지 닫혀있는지 확인 할 수 있는 state를 useState로 관리한다.
 
 const [cartItems, setCartItems] = useState([] as CartItemType[])
  * 받아온 모든 상품을 관리하는 state이다.
 
 const {data, isLoading, error} = useQuery<CartItemType[]>(
    'products',
     getProducts
     );
     * useQuery<type>('key', function) => type은 받아온 데이터의 타입을 의미한다.
     * data는 getProducts로 받아온 json형태의 자료를 의미한다.

    console.log(data);
    const getTotalItems = (items: CartItemType[]) => 
      items.reduce((ack:number, item) => ack + item.amount, 0)
	* reduce(ack, cur, idx, src(원본배열))
	* reduce(function, 초기값)
	* reduce() 메서드는 배열의 각 요소에 대해 주어진 리듀서(reducer) 함수를 실행하고, 하나의 결과값을 반환합니다.

    const handleAddToCart = (clickedItem: CartItemType) => {
       setCartItems(prev => { 
	* useState에서 set함수를 이용할 때 첫번째 인자는 해당 state이다.
        
         const isItemInCart = prev.find(item => item.id === clickedItem.id)

         if(isItemInCart) {
           return prev.map(item => (
             item.id === clickedItem.id ? {...item, amount: item.amount + 1} : item
           ));
         }

         return [...prev, {...clickedItem, amount: 1}];
       });
     };
	* Cart에 새로운 상품을 추가하는 함수이다.
	* 1.Cart에 같은 상품이 있는지 확인한다.
	* 2.만약에 있다면 그 상품의 amount를 1 증가시키고 아니라면 amount를 1로 만든다
 


     const handleRemoveFromCart = (id: number) => {
       setCartItems(prev => 
         prev.reduce((ack, item) => {
            if(item.id === id){
              if(item.amount === 1) return ack;
              return [...ack, {...item, amount: item.amount - 1}];
            } else{
              return [...ack, item];
            }
          }, [] as CartItemType[])
        );
    };
    * Cart에서 상품을 삭제하는 함수이다.
    * 1.인자로 준 id와 일치하는 상품이 있는지 확인한다
    *	1-1 id와 일치하는 상품의 수량이 1인지 확인한다
    * 		1-1-1 있다면 CartItems 배열에서 삭제한다
    *		1-1-2 없다면 그 상품의 수량은 2개 이상이고 수량을 -1 한다.
    *	1-2 없다면 [...ack, item]을 반환한다.

     if(isLoading) return <LinearProgress />
	* 로딩하는 동안 나오는 화면이다.
     
     if (error) return <div>Something is wrong...</div>
	* error 예외처리
    
    
  return (
    <Wrapper>
      <Drawer anchor='right' open={cartOpen} onClose={() => setCartOpen(false)}>
    	* anchor : Drawer의 위치 
        * open : open시 실행할 함수
       
        <Cart cartItems={cartItems} addToCart={handleAddToCart} removeFromCart={handleRemoveFromCart}/>
          * cartItems : Cart속에 있는 상품들이다.
   
      </Drawer>
      <StyledButton onClick={() => setCartOpen(true)}>
        <Badge badgeContent={getTotalItems(cartItems)} color='primary'>
          * Badge Component는 child의 top-right에 작은 뱃지를 만든다.
          * badgeContent : badge안에 들어갈 내용을 의미한다.
                            
          <AddShoppingCartIcon />
        </Badge>
      </StyledButton>
      <Grid container spacing={3}>
        {data?.map((item => (
          <Grid item key={item.id} xs={12} sm={4}>
            <Item item={item} handleAddToCart={handleAddToCart} />
          </Grid>
        )))}
      </Grid>
	* Container와 Item이라는 두 가지 개념으로 구분되어 있다.
	  Container는 Items를 감싸는 부모 요소이며, 그 안에서 각 Item을 배치할 수 있다.
      	* spacing : spacing = spacing * 8px,
          e.g. spacing={2} creates a 16px wide gap.
        * lg, md, sm, xl, xs 속성에 1에서 12 사이의 정수를 입력하면 
          전체의 ((입력한 값)/12) % 만큼 너비를 차지하게 된다.
    </Wrapper>
  );
}

export default App;

getProuct의 결과(CartItemType[])

item (CartItemType)

Item.tsx

import Button from '@material-ui/core/Button';
import {CartItemType} from '../App';
import {Wrapper} from './Item.styles';

type Props = {
    item: CartItemType;
    handleAddToCart: (clickedItem: CartItemType) => void;
}
* Props의 type을 정의한다.

const Item: React.FC<Props> = ({item, handleAddToCart}) => {

    return(
    <Wrapper>
        <img src={item.image} alt={item.title} />
        <div>
            <h3>{item.title}</h3>
            <p>{item.description}</p>
            <h3>${item.price}</h3>
        </div>
        <Button onClick={() => handleAddToCart(item)}>Add to cart</Button>
    </Wrapper>
    );
* React.FC<Props> : Item함수의 반환형은 react component이고 props의 type은 Props이다.

};
export default Item;

Cart.tsx

import CartItem from '../CartItem/CartItem';
import {Wrapper} from './Cart.styles';
import {CartItemType} from '../App';

type Props = {
    cartItems: CartItemType[];
    addToCart: (clickedItem: CartItemType) => void; // Function : addTocart, return value : void
    removeFromCart: (id: number) => void;
};
* 넘겨받을 props의 타입을 정의한다.
* addToCart, removeFromCart는 기능만 갖고 있으므로 반환값이 void이다.

const Cart: React.FC<Props> = ({cartItems, addToCart, removeFromCart}) => {
    const calculateTotal = (items: CartItemType[]) => 
        items.reduce((ack: number, item) => ack + item.amount * item.price, 0)
	* amount와 price를 곱해서 총 얼마인지 계산한다.

    return (
        <Wrapper>
            <h2>Your Shopping Cart</h2>
            {cartItems.length === 0 ? <p>No items in cart.</p> : null}
            {cartItems.map(item => (
                <CartItem 
                    key={item.id}
                    item={item}
                    addToCart={addToCart}
                    removeFromCart={removeFromCart}
                />
            ))}
            <h2>Total: ${calculateTotal(cartItems).toFixed(2)}</h2>
		* toFixed() : 소수점 설정
        </Wrapper>
            );
};

export default Cart;

CartItem.tsx

import Button from '@material-ui/core/Button'
import {CartItemType} from '../App';
import {Wrapper} from './CartItem.styles';

type Props = {
    item: CartItemType;
    addToCart: (clickedItem: CartItemType) => void;
    removeFromCart: (id: number) => void;
}

const CartItem: React.FC<Props> = ({item, addToCart, removeFromCart}) => (
    <Wrapper>
        <div>
            <h3>{item.title}</h3>
            <div className="information">
                <p>Price: ${item.price}</p>
                <p>Total: ${(item.amount * item.price).toFixed(2)}</p>
            </div>
            <div className="buttons">
                <Button
                    size="small"
                    disableElevation
                    variant="contained"
                    onClick={() => removeFromCart(item.id)}
		    >
                        -
                </Button>
                <p>{item.amount}</p>
                <Button
                    size="small"
                    disableElevation
                    variant="contained"
                    onClick={() => addToCart(item)}
		    >
                        +
                </Button>
            </div>
        </div>
        <img src={item.image} alt={item.title} />
    </Wrapper>
);

export default CartItem;
profile
https://github.com/Coaspe

0개의 댓글