React - JS / TS

์ด๋™์–ธยท2024๋…„ 9์›” 21์ผ

new world

๋ชฉ๋ก ๋ณด๊ธฐ
46/62
post-thumbnail

9.21 (๊ธˆ)

1. TailWind CSS

๐Ÿ‘‰ styleํƒœ๊ทธ๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ณ„๋„์˜ css๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , javaํƒœ๊ทธ ๋‚ด๋ถ€์— css๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ.

ex)

<h1 className="h1 bg-red-400"> hello </h1>

๐Ÿ“Œ https://tailwindcss.com/docs/guides/vite

  1. npm install -D tailwindcss postcss autoprefixer
  2. npx tailwindcss init -p
  3. tailwind.config ๋‚ด๋ถ€์˜ content์— ํ•ด๋‹น๋‚ด์šฉ ์ˆ˜์ •
content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  1. index.css๋‚ด๋ถ€์— ๊ธฐ์กด๋‚ด์šฉ ๋ชจ๋‘ ์ง€์šฐ๊ณ  ํ•ด๋‹น๋‚ด์šฉ ๋ณต์‚ฌ
@tailwind base;
@tailwind components;
@tailwind utilities;
  1. App.jsx ์˜ return๊ฐ’์˜ ๋‚ด์šฉ์„ ์ˆ˜์ •ํ•˜์—ฌ ํ™•์ธํ•ด๋ณด๊ธฐ




2. React - JS

2-1. KioskMain.jsx

import CartDiv from "./CartDiv.jsx";
import ProductsList from "./ProductsList.jsx";
import {useState} from "react";

function KioskMain() {

    const products = [
        {pid:1, pname:'M1', price:8000, kind:'N', img:'http://localhost:8081/food/M1.jpeg'},
        {pid:2, pname:'M2', price:1000, kind:'C', img:'http://localhost:8081/food/M2.jpeg'},
        {pid:3, pname:'M3', price:2000, kind:'N', img:'http://localhost:8081/food/M3.jpeg'},
        {pid:4, pname:'M4', price:3000, kind:'C', img:'http://localhost:8081/food/M4.jpeg'},
        {pid:5, pname:'M5', price:4000, kind:'N', img:'http://localhost:8081/food/M5.jpeg'},
    ]

    // cartItems - ์นดํŠธ ๋‚ด๋ถ€์ƒํƒœ, setCartItems - ์นดํŠธ์— ๋‹ด๊ฒผ์„๋•Œ ๋‹ค์Œ ์ƒํƒœ
    const [cartItems, setCartItems] = useState([])

    // Cart์— ๋‹ด๋Š” ๊ธฐ๋Šฅ
    const addToCart = (newProduct) => { // newProduct = ์ƒˆ๋กœ ์ถ”๊ฐ€๋˜๋Š” ๋ฌผํ’ˆ

        const target = cartItems.find( item => { // item = [{product{pid,pname,price,kind}, qty:1 }]
            const p = item.product
            console.log("item.product : " + p)

            const result = p.pid === newProduct.pid // ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์žˆ๋Š” product์˜ pid์™€ ์ƒˆ๋กœ๋“ค์–ด์˜จ pid
            console.log("์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์žˆ๋Š”์ง€? " + result)
            return result ? p : null
        })

        if(!target) {
            setCartItems([...cartItems, {product: newProduct, qty: 1}]) // ๊ธฐ์กด์นดํŠธ๊ฐ’์— ์ƒˆ๋กœ๋“ค์–ด์˜จ ๋ฌผํ’ˆ์„ ์ถ”๊ฐ€, ์ˆ˜๋Ÿ‰์ •์˜
            return
        }

        target.qty += 1 // ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์žˆ๋‹ค๋ฉด, ์ˆ˜๋Ÿ‰๋งŒ ๋ณ€๊ฒฝ
        setCartItems([...cartItems]) // ๊ฐ์ฒด ์ƒˆ๋กœ์ƒ์„ฑ


    }

    const changeCartItem = (pid, amount) => {

        const target = cartItems.find( item => item.product.pid === parseInt(pid))  // item = [{product{pid,pname,price,kind}, qty:1 }]

        if(!target) {
            return
        }

        target.qty += amount

        if(target.qty <= 0) {
            const filtered = cartItems.filter(item => item.product.pid !== parseInt(pid))
            // ๋‚ด๊ฐ€ ์„ ํƒํ•˜์ง€ ์•Š์€ pid๊ฐ’๋งŒ ๋‚จ๊ธฐ๊ธฐ
            setCartItems(filtered)
            return
        }

        setCartItems([...cartItems])
    }

    return (
        <div className="w-full flex">
            <div className='border-2 w-2/3 bg-blue-400'>
                <ProductsList products={products} addToCart={addToCart}></ProductsList>
            </div>

            <div className='border-2 w-1/3 bg-red-400'>
                <CartDiv cartItems={cartItems} changeCartItem={changeCartItem} ></CartDiv>
            </div>
        </div>
    );
}

export default KioskMain;

๐Ÿ‘‰ KioskMain์—์„œ๋Š” products, cartItems, setCartItems, addToCart, ChangeCartItem์„ ์ •์˜ํ•˜์—ฌ CartDiv, ProductsList ๊ฐ๊ฐ์˜ ์ปดํฌ๋„ŒํŠธ์— ๋ถ€์—ฌํ•œ๋‹ค.

๐Ÿ‘‰ products๋Š” ๋ฌผํ’ˆ๊ณผ ๊ฐ€๊ฒฉ์ด ์ถœ๋ ฅ๋˜์–ด์•ผํ•˜๊ณ , addToCart๋Š” ๋ฌผํ’ˆ์ด ๋‹ด๊ฒจ์•ผํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋ฏ€๋กœ ProductList์— ๋ถ€์—ฌํ•œ๋‹ค.

๐Ÿ‘‰ cartItems๋Š” ์นดํŠธ์— ๋‹ด๊ธด ์ƒํƒœ๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ , ChangeCartItem์€ ์žฅ๋ฐ”๊ตฌ๋‹ˆ์—์„œ ์ˆ˜๋Ÿ‰์„ ๋ณ€๊ฒฝํ•ด์•ผํ•˜๋ฏ€๋กœ CartDiv์— ๋ถ€์—ฌํ•œ๋‹ค.




2-2. ProductsList.jsx


function ProductsList({products,addToCart}) {

    const liList = products.map( p => { // ์—ฌ๊ธฐ์„œ p๊ฐ’์€ product(๊ฐ ์ƒํ’ˆ๋“ค์„ ๋œปํ•จ. 1๋ฒˆ์ƒํ’ˆ , 2๋ฒˆ์ƒํ’ˆ์„ ์ˆœํšŒ)
        const {pid, pname, price, kind, img} = p

        return <li className="w-1/6 border-2 h-1/6 min-h-[10rem]" key={pid} onClick={() => addToCart(p)}>
            <img src={img}/>
            {pname} -- {price}
               </li>
    })


    return (
        <div>
            <h1>Products List</h1>
            <div className='w-full h-full'>
            <ul className='flex gap-2 flex-auto p-6'>
                {liList}
            </ul>
            </div>
        </div>
    );
}

export default ProductsList;

๐Ÿ‘‰ Main์—์„œ ๋ถ€์—ฌ๋ฐ›์€ products,addToCart ๋‘๊ฐœ์˜ ๊ธฐ๋Šฅ์„ ๋ถ€์—ฌํ•˜๊ณ , products์—์„œ ๊ฐ๊ฐ์˜ ์ƒํ’ˆ(m1,m2,m3...)์ด ์žˆ์„ํ…๋ฐ, ๊ทธ ์ƒํ’ˆ์˜ ์†์„ฑ๊ฐ’์„ ์ถœ๋ ฅํ•˜๊ธฐ ์œ„ํ•จ.

๐Ÿ‘‰ addToCart์—์„œ๋Š” ์„ ํƒํ•œ ์ƒํ’ˆ์˜ ๊ตฌ์กฐ๋ถ„ํ•ดํ• ๋‹น๋œ ๊ฐ’์„ ๋„ฃ๋Š”๋‹ค.




2-3. CartDiv.jsx

function CartDiv({cartItems, changeCartItem}) {

    const cartItemsTag = cartItems.map((item) => { // item = {product{pid,pname,price,kind}, qty}

        const product = item.product;
        const qty = item.qty;

        return (
            <li key={product.pid} className='m-2 p-2'>
                {product.name}: {qty} : {product.price * qty}
                <button className='bg-blue-400 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded'
                        onClick={() => changeCartItem(product.pid, 1)}>+
                </button>

                <button className='bg-red-400 hover:bg-red-700 text-white font-bold py-2 px-4 rounded'
                        onClick={() => changeCartItem(product.pid, -1)}>-
                </button>
            </li>
        )
    })

    return (
        <div>
            <h1>Cart Div</h1>
            <div className='w-full h-full'>
                {cartItemsTag}
            </div>
        </div>
    );
}

export default CartDiv;

๐Ÿ‘‰ main์—์„œ ๋ฐ›์€ cartItems๋ฅผ ํ†ตํ•ด ์นดํŠธ์— ๋‹ด๊ธด ๋ฌผํ’ˆ๋“ค์„ ์ถœ๋ ฅํ•œ๋‹ค.
์—ฌ๊ธฐ์„œ item๊ฐ’์€ item = {product{pid,pname,price,kind}, qty} ์ž„์„ ์ƒ๊ฐํ•˜์ž.

๐Ÿ‘‰ ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„๋•Œ, CartItems๋‚ด๋ถ€์˜ product๋‚ด๋ถ€ pid์†์„ฑ๊ฐ’๊ณผ, amount์ธ 1๊ฐ’์ด ์ „๋‹ฌ๋˜์–ด ์ˆ˜๋Ÿ‰์ด ๋ณ€๊ฒฝ๋œ๋‹ค.




3. React - TS

3-1. kiosk.ts

export interface IProduct {
    pid : number;
    pname : string;
    price : number;
    img? : string;
    kind? : string;
}

export interface ICartItem {
    product : IProduct, // product๋Š” IProduct์˜ ์†์„ฑ๊ฐ’๋“ค์„ ๊ฐ€์ง€๋ฏ€๋กœ ํ•ด๋‹น ๋ฐ์ดํ„ฐํƒ€์ž…์„ ๊ฐ–๊ฒŒ๋œ๋‹ค.
    qty : number
}

๐Ÿ‘‰ typeScript๋Š” type ํŒจํ‚ค์ง€๋ฅผ ๋”ฐ๋กœ ์ƒ์„ฑํ•ด์„œ ์†์„ฑ๊ฐ’๊ณผ ๋ฐ์ดํ„ฐํƒ€์ž…์„ ๋ฏธ๋ฆฌ ๊ตฌ์กฐํ•œ๋‹ค.

๐Ÿ‘‰ ์—ฌ๊ธฐ์„œ img? ์™€ ๊ฐ™์€ ?๋Š” ์žˆ์„์ˆ˜๋„์žˆ๊ณ  ์—†์„์ˆ˜๋„์žˆ๋‹ค๋Š” ์˜๋ฏธ




3-2. KioskMain.tsx

import ProductList from "./ProductList.tsx";
import CartDiv from "./CartDiv.tsx";
import {ICartItem, IProduct} from "../../type/kiosk.ts";
import {useState} from "react";


function KioskMain() {

    const products : IProduct = [
        {pid:1, pname:'M1', price:3000, img:'http://localhost:8081/food/M1.jpeg'},
        {pid:2, pname:'M2', price:4000, img:'http://localhost:8081/food/M2.jpeg'},
        {pid:3, pname:'M3', price:5000, img:'http://localhost:8081/food/M3.jpeg'},
        {pid:4, pname:'M4', price:6000, img:'http://localhost:8081/food/M4.jpeg'},
        {pid:5, pname:'M5', price:7000, img:'http://localhost:8081/food/M5.jpeg'},
    ]

    const [ cartItems, setCartItems ] = useState<ICartItem[]>([]) // cartItems๋Š” ICartItem[]์˜ ํƒ€์ž…์„ ๊ฐ€์ง„๋‹ค.

    const addToCart = (newProduct: IProduct):void => { // ํŠน์ •ํ•œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š”๊ฒƒ์ด ์•„๋‹ˆ๋ฏ€๋กœ void

        const target: ICartItem|undefined = cartItems.find( item => item.product.pid === newProduct.pid)
        // find์— ๋งŒ์กฑํ•˜๋Š” ๊ฐ’์ด ์—†๋‹ค๋ฉด undefined๋ฅผ ๋ฐ˜ํ™˜ -> ์„ ํƒํ•œ ๋ฌผ๊ฑด์ด ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์—†๋‹ค๋ฉด
        if(!target) {
            setCartItems([...cartItems,  {product:newProduct, qty:1}])
            return;
        }
        else{
            target.qty += 1
            setCartItems([...cartItems])
        }
    }

    const changeQty = (pid:number, qty:number):void => {

        const target: ICartItem|undefined = cartItems.find( item => item.product.pid === pid)

        if(!target) {
            return;
        } else {
            target.qty += qty
            setCartItems([...cartItems])
        }
    }


    return (
        <div className='w-full h-full p-3 flex'>

            <div className='w-2/3 bg-blue-400 m-2 p-2'>
                <h1 className='p-2 font-bold' >Product List</h1>
                <ProductList products={products} addToCart={addToCart}></ProductList>
            </div>


            <div className='w-1/3 bg-red-400 m-2 p-2'>
                <h1 className='p-2 font-bold'>Cart List</h1>
                <CartDiv cartItems={cartItems} changeQty={changeQty}></CartDiv>
            </div>
        </div>
    );
}

export default KioskMain;

๐Ÿ‘‰ cartItems์™€ ๊ฐ™์€ ์นดํŠธ ๋‚ด๋ถ€์˜ ์ƒํƒœ ๋ฐ์ดํ„ฐ๋Š” ICartItem[] ์˜ ๋ฐ์ดํ„ฐํƒ€์ž…์„ ๊ฐ€์ง„๋‹ค.

๐Ÿ‘‰ addCart์—์„œ ์ƒˆ๋กญ๊ฒŒ ๋‹ด๊ธฐ๋Š” product์˜ ํƒ€์ž…์€ IProduct์˜ ํƒ€์ž…์ด๋ฏ€๋กœ ๋ฏธ๋ฆฌ ์„ ์–ธํ•˜๊ณ , target์ด ์žˆ๋‹ค๋ฉด ICartItem์˜ ํƒ€์ž…์„ ๊ฐ–๊ฒŒ ๋˜๊ณ , ์—†๋‹ค๋ฉด undefined ํƒ€์ž…์„ ๊ฐ–๋Š”๋‹ค.




3-3. ProductList.tsx

import {IProduct} from "../../type/kiosk.ts";
import {ReactElement} from "react";

interface ProductListProps {
    products : IProduct[];
    addToCart : (newProduct: IProduct) => void;
}


function ProductList({products, addToCart} : ProductListProps): ReactElement {

    console.log(products);

    const productLI = products.map((product: IProduct) => {
        const {pid, pname, price, img} = product;

        return (
            <li className='border-2 w-1/5' key={pid} onClick={() => addToCart(product)}>
                {img && <img src={img} alt={pname}/>}
                {pname} -- {price}
            </li>
        )
    })


    return (
        <div>
            <ul className='flex flex-wrap'>
                {productLI}
            </ul>

        </div>
    );
}

export default ProductList;

๐Ÿ‘‰ js์™€ ๋‹ค๋ฅด๊ฒŒ ts์—์„œ๋Š” ๋ถ€์—ฌ๋ฐ›์€ ๊ฐ์ฒด ๋ฐ ๋ฉ”์„œ๋“œ๋Š” direct๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , interface๋กœ ์ƒ์œ„์— ์ •์˜ํ•œ๋‹ค์Œ ์•„๋ž˜์—์„œ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค. ์•„๋ž˜์—์„œ๋Š” ({products, addToCart} : ProductListProps) ์ด์ฒ˜๋Ÿผ ๋ถ€์—ฌ๋ฐ›์€ ๋‚ด์šฉ๋“ค์„ ์ž‘์„ฑํ•˜๊ณ , ReactElement๋Š” React ์ปดํฌ๋„ŒํŠธ๋กœ์„œ ์œ ํšจํ•œ React ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

๐Ÿ‘‰ ์•„๋ž˜์ฝ”๋“œ๋Š” img๊ฐ€ ์žˆ์„๊ฒฝ์šฐ img๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค๋Š” ์˜๋ฏธ

 {img && <img src={img} alt={pname}/>}




3-4. CartDiv.tsx

import {ICartItem, IProduct} from "../../type/kiosk.ts";
import {ReactElement} from "react";

interface CartListProps{
    cartItems : ICartItem[]
    changeQty : (pid: number, qty: number) => void;
}

function CartDiv({cartItems, changeQty}: CartListProps ):ReactElement {

    console.log(cartItems)

    const listLI = cartItems.map((item: ICartItem) => {
        const {product, qty} = item
        return <li key={product.pid} className='flex flex-wrap border-2 gap-3'>
            {product.img && <img className='w-1/6' src={product.img}/>}
            {product.pname} -- {qty} -- {product.price * qty}
            <button onClick={()=> changeQty(product.pid,1)}>+</button>
            <button onClick={()=>changeQty(product.pid,-1)}>-</button>
        </li>
    })

    return (
        <div>
            {listLI}
        </div>
    );
}

export default CartDiv;

๐Ÿ‘‰ ๋ถ€์—ฌ๋ฐ›์€ ๊ฐ์ฒด์™€ ๋ฉ”์„œ๋“œ๋ฅผ interface๋กœ ์ •์˜ํ•˜๊ณ , changeQty๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ’๊ณผ ,return๊ฐ’์ธ void๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

interface CartListProps{
    cartItems : ICartItem[]
    changeQty : (pid: number, qty: number) => void;
}

๐Ÿ‘‰ ์•„๋ž˜์ฝ”๋“œ์—์„œ๋Š” return๊ฐ’์˜ ๋ณต์žกํ•จ์„ ์ค„์ด๊ธฐ์œ„ํ•ด listLI๋ผ๋Š” ๋ณ€์ˆ˜๊ฐ’์— return๊ฐ’์„ ๋ณ„๋„๋กœ ๋งŒ๋“ค์–ด ์ตœ์ข… return๊ฐ’์—์„œ๋Š” listLI๋ณ€์ˆ˜๋งŒ ์‚ฌ์šฉํ•˜๋„๋ก ํ•œ๋‹ค.

 const listLI = cartItems.map((item: ICartItem) => {
        const {product, qty} = item
        return <li key={product.pid} className='flex flex-wrap border-2 gap-3'>
            {product.img && <img className='w-1/6' src={product.img}/>}
            {product.pname} -- {qty} -- {product.price * qty}
            <button onClick={()=> changeQty(product.pid,1)}>+</button>
            <button onClick={()=>changeQty(product.pid,-1)}>-</button>
        </li>
    })




4. TypeScript์˜ ์žฅ์ 

  1. ํƒ€์ž…์˜ ์•ˆ์ •์„ฑ : JS์™€ ๋‹ฌ๋ฆฌ TS๋Š” type์ปดํฌ๋„ŒํŠธ๋‚ด๋ถ€์˜ interface๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ œํ’ˆ๊ณผ ์žฅ๋ฐ”๊ตฌ๋‹ˆ์˜ ๊ตฌ์กฐ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์—ฌ, ๋ฐ์ดํ„ฐํƒ€์ž…์˜ ๊ตฌ์กฐ์— ๋Œ€ํ•œ ์˜ค๋ฅ˜๊ฐ€ ์ค„์–ด๋“ ๋‹ค.

  2. addToCart์—์„œ์˜ ๋ฐ์ดํ„ฐํƒ€์ž… ๋ช…์‹œ, changeQty์—์„œ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ช…์‹œํ•˜์—ฌ ์‹ค์ˆ˜๊ฐ€ ์ค„์–ด๋“ฌ

  3. ์œ ๋‹ˆ์–ธํƒ€์ž… : const target: ICartItem|undefined ์™€ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ณ€์ˆ˜์˜ ์—ฌ๋Ÿฌํƒ€์ž…์„ ๋ช…์‹œ๊ฐ€๋Šฅ

0๊ฐœ์˜ ๋Œ“๊ธ€