๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
IT/React

[Spring Boot, react] Spring Boot ๋ฐ์ดํ„ฐ react์— ๊ฐ€์ ธ์˜ค๊ธฐ

by ITyranno 2023. 11. 1.
728x90
๋ฐ˜์‘ํ˜•

 

 

ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์„ธ๊ณ„๋ฅผ ํƒ๊ตฌํ•ฉ์‹œ๋‹ค.

 

 

ํ•ด๋‹น ํŽ˜์ด์ง€์˜ ํ”„๋ก ํŠธ๋Š” react๋กœ ๊ตฌ์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋Š” springboot๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค.

์ „๊ธฐ์ฐจ์— ๋”ฐ๋ฅธ ์ฃผํ–‰๊ฑฐ๋ฆฌ๋ฅผ ์•Œ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ฐจ๋Ÿ‰์„ ๊ฒ€์ƒ‰ํ•˜๊ณ  ํด๋ฆญํ•˜์—ฌ ์ƒ๋‹จ์— ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

 

 

๊ตฌํ˜„ ํ™”๋ฉด

 

 

 

 

์ž‘์„ฑ ์ฝ”๋“œ

 

 

 

(1) Spring Boot

 

 

CarDataController.java

 

package com.example.board.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.board.model.CarData;
import com.example.board.repository.CarDataRepository;

@RestController
public class CarDataController {

    @Autowired
    private CarDataRepository carRepo;

    @GetMapping("/media/cardata")
    public List<CarData> getAllCars() {
        return carRepo.findAll();
    }
}

 

 

CarData.java

 

package com.example.board.model;

import javax.persistence.Entity;
import javax.persistence.Id;

import lombok.Data;

@Entity
@Data
public class CarData {
    @Id
    Integer id;
    String model;
    String km;
}

 

 

CarDataRepository.java

 

package com.example.board.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.board.model.CarData;

public interface CarDataRepository extends JpaRepository<CarData, Integer> {
}

 

 

 

(2) react

 

 

App.js

 

import React, { useState, useEffect } from 'react';
import Cardata from './cardata/Cardata';
import Header from './header/Header';
import Loading from './loading/Loading';
import './App.css';

function App() {
  const [message, setMessage] = useState("");
  const [isLoading, setIsLoading] = useState(true); // ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌ

  useEffect(() => {
    // ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋น„๋™๊ธฐ ์ž‘์—…์„ ์‹œ๋ฎฌ๋ ˆ์ดํŠธ
    setTimeout(() => {
      fetch('/media/cardata')
        .then(response => response.text())
        .then(message => {
          setMessage(message);
          setIsLoading(false); // 2์ดˆ ํ›„์— ๋กœ๋”ฉ ์™„๋ฃŒ๋กœ ์„ค์ •
        });
    }, 2500); // 2์ดˆ ์ง€์—ฐ
  }, []);

  return (
    <>
      {isLoading ? (
        <Loading />
      ) : (
        <>
          <Header />
          <Cardata />
        </>
      )}
    </>
  );
}

export default App;

 

 

package.json

 

"proxy": "http://localhost",
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },

 

 

Cardata.jsx

 

import { useState, useEffect } from 'react';
import style from './Cardata.module.css'
import ReactPlayer from 'react-player';

const Cardata = () => {
    const [carDatas, setCarDatas] = useState([]);
    const [searchTerm, setSearchTerm] = useState("");
    const [selectedItems, setSelectedItems] = useState([]);

    useEffect(() => {
        fetch("/media/cardata")
            .then((res) => res.json())
            .then((data) => setCarDatas(data));
    }, []);

    const toggleSelect = (item) => {
        if (selectedItems.includes(item)) {
            setSelectedItems(selectedItems.filter((selectedItem) => selectedItem !== item));
        } else {
            setSelectedItems([...selectedItems, item]);
        }
    }


    return (
        <div className={style['react']}>
            <p className={style['right-top-text']}>react + springboot ํ™œ์šฉํ•œ ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค.</p>
            <div className={style['car-data-title']}>
                <div>
                    <h1>์ „๊ธฐ์ฐจ ์ฃผํ–‰๊ฑฐ๋ฆฌ</h1>
                </div>
                <div>
                    <p className={style['car-data-dis']}>์ „๊ธฐ์ฐจ ์ฃผํ–‰๊ฑฐ๋ฆฌ : ํ•œ ๋ฒˆ ์ถฉ์ „์œผ๋กœ ์ฃผํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฑฐ๋ฆฌ</p>
                </div>
            </div>
            <div className={style['container']}>
                <div className={style['car-data-container']}>
                    <ReactPlayer
                        url="https://www.youtube.com/watch?v=m3J1TAF93pQ"
                        playing={true}
                        loop={true}
                        muted={true}
                        width="100%"
                        height="100%"
                        style={{ position: 'absolute', top: 0, left: 0, zIndex: -1 }}
                    />
                    <p className={style['car-data-intro']}>์ฃผํ–‰๊ฑฐ๋ฆฌ ์ƒ์œ„ 250๊ฐœ ์ „๊ธฐ์ž๋™์ฐจ ๋ชฉ๋ก์ž…๋‹ˆ๋‹ค.<br /><br />
                        ์•Œ๊ณ  ์‹ถ์€ ์ฐจ์ข…์„ ๊ฒ€์ƒ‰ํ•ด ๋ณด์„ธ์š”!</p>
                    <div className={style['search-bar']}>
                        <input
                            type="text"
                            placeholder="์ฐจ ์ข…๋ฅ˜ ์ž…๋ ฅ"
                            onChange={(event) => {
                                setSearchTerm(event.target.value);
                            }}
                        />
                        <button className={style['search-button']}>๊ฒ€์ƒ‰</button>
                    </div>
                </div>

                <div className={style['results-container']}>
                    <h2 className={style['selected-items-title']}>๊ฒ€์ƒ‰ ๊ธฐ๋ก</h2>
                    {selectedItems.map((item, index) => (
                        <div key={index}>
                            <p>{item} <button onClick={() => toggleSelect(item)}>x</button></p>
                        </div>
                    ))}
                    {carDatas.filter((val) => {
                        if (searchTerm === "") {
                            return val;
                        } else if (val.model.toLowerCase().includes(searchTerm.toLowerCase())) {
                            return val;
                        }
                    }).map((car) => (
                        <div key={car.id} className={style['car-item']}>
                            <a href="#" onClick={(e) => { e.preventDefault(); toggleSelect(`${car.model} - ${car.km} km`); }}>{car.model} - {car.km} km</a>
                        </div>
                    ))
                    }
                </div>
            </div>
        </div >
    );
}

export default Cardata;

 

 

 

Cardata.module.css

 

h1 {
    font-size: 3.5rem;
    margin-bottom: 5rem;
    color: #f2c12e;
}

.selected-items-title {
    text-align: center;
    width: 94rem;
    font-size: 1.7rem;
    height: 4rem;
}

p {
    text-align: center;
    margin-bottom: 3rem;
}


.car-data-container {
    text-align: center;
    margin-bottom: 3rem;
    padding: 1rem;
    border-radius: 5px;
    font-family: 'TheJamsil5Bold';
    animation: fadeIn 1.5s ease-in-out;
    font-size: 2rem;

}

.car-data-title {
    position: relative;
    text-align: center;
    border-radius: 5px;
    font-family: 'TheJamsil5Bold';
    animation: fadeIn 1.5s ease-in-out;
}

.car-data-dis {
    font-size: 1.8rem;
    margin-bottom: 1.5rem;
}


.hidden {
    display: none;
}


@keyframes slideFromLeft {
    from {
        transform: translateX(-100%);
        opacity: 0;
    }

    to {
        transform: translateX(0);
        opacity: 1;
    }
}

@keyframes fadeIn {
    from {
        opacity: 0;
        transform: translateY(-20px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.hidden .car-item {
    opacity: 0;
    transform: scale(0);
}

.car-data-intro {
    margin-bottom: 5rem;
}



.search-bar {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 10px;
}

input[type='text'] {
    width: 300px;
    height: 30px;
}

.search-button {
    width: 70px;
    height: 35px;
    background-color: #f2c12e;
    color: black;
    border: none;
    cursor: pointer;
}

.car-list {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;

}

.car-item {
    width: 300px;
    background-color: #f9f9f9;
    padding-top: 10px;
    padding-bottom: 10px;
    margin: 0 auto;
    /* ์ˆ˜์ง ๋งˆ์ง„์€ 0, ์ˆ˜ํ‰ ๋งˆ์ง„์€ ์ž๋™์œผ๋กœ ์„ค์ •ํ•˜์—ฌ ๊ฐ€์šด๋ฐ ์ •๋ ฌ */
}

strong {
    font-weight: bold;
}

.car-item {
    font-family: 'TheJamsil5Bold';
    text-align: center;
}


.Km {
    color: black;
}



@font-face {
    font-family: 'TheJamsil5Bold';
    src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2302_01@1.0/TheJamsil5Bold.woff2') format('woff2');
    font-weight: 700;
    font-style: normal;
}

.right-top-text {
    position: absolute;
    top: 2rem;
    right: 1rem;
}

.results-container {
    height: 30rem;
    /* ์›ํ•˜๋Š” ๋†’์ด๋กœ ์„ค์ •ํ•˜์„ธ์š”. */
    overflow-y: auto;
    /* ์ˆ˜์ง ์Šคํฌ๋กค์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. */
}

 

 

 

์œ„ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜์—ฌ react์™€ spring boot๋ฅผ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

728x90
๋ฐ˜์‘ํ˜•

loading