๊ทธ๋™์•ˆ 17ํŽธ์„ ๊ฑฐ์ณ์„œ ์›น ๊ฐœ๋ฐœ React&Python์œผ๋กœ ์›น๊ฐœ๋ฐœ(๋งํฌ์„œ๋น„์Šค)๋ฅผ ๊ฐœ๋ฐœ ํ–ˆ์œผ๋ฉฐ, 

ํ˜ผ์ž ํ”„๋ก ํŠธ/๋ฐฑ์—”๋“œ๋ฅผ ํ•˜๋‹ค ๋ณด๋ฉด ๋ณต์žกํ•จ์„ ๋Š๋ผ๋ฉฐ, ์ˆ˜์ •์ž‘์—…์ด ์ด๋ฃจ์–ด์ง€๋ฉด ๋ฐฑ์—”๋“œ์™€ ํ”„๋ก ํŠธ์—”๋“œ ์—ฌ๋Ÿฌ ์†Œ์Šค๋“ค์„ ๊ฑฐ์ณ์„œ ๊ด€๋ จ ๋‚ด์šฉ์„ ๋ฐ˜์˜ํ•ด์•ผ ํ•˜๋Š” ์–ด๋ ค์›€์ด ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ, ๋ฐ˜๋ณต์ ์ธ ๊ฐœ๋ฐœ ๊ณผ์ •์„ ํ†ตํ•ด์„œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์‹ค๋ ฅ์„ ๋†’์ผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

 

์ตœ๋Œ€ํ•œ STEP BY STEP๋กœ ์†Œ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด์„œ ์„ค๋ช…ํ•˜๋ ค๊ณ  ๋…ธ๋ ฅํ–ˆ์œผ๋‚˜,

๋” ๋ณต์žกํ•ด ์กŒ๋Š”์ง€ ์•Œ ์ˆ˜ ์—†์ง€๋งŒ, ์ผ๋‹จ ์ฐจ๊ทผ์ฐจ๊ทผ ๋”ฐ๋ผํ•ด๋ณด๋ฉด ์ข‹์„ ๋“ฏ ํ•˜๋‹ค.

 

๋˜ํ•œ ๋ณธ ์†Œ์Šค๋ฅผ ๊ณต๊ฐœํ•˜๋ฉด์„œ ์—ฌ๋Ÿฌ๋ถ„๊ณผ ํ•จ๊ป˜ WIN-WIN์ด ๋  ์ˆ˜ ์žˆ์„ ๊ฑฐ๋ผ๊ณ  ๋ฏฟ์œผ๋ฉฐ, ์ˆ˜์ •๊ณผ ๋ฐœ์ „์„ ํ•ด ๊ฐ€๋ฉด์„œ ์—ฌ๋Ÿฌ๋ถ„๋“ค ๋งŒ์˜ ๋ ˆํผ๋Ÿฐ์Šค ์†Œ์Šค๊ฐ€ ๋˜๊ธธ ๋ฐ”๋ž€๋‹ค.

 

๋ˆ„๊ตฌ๋‚˜ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๋Š” ์†Œ์Šค์ด๋ฉฐ, ๋‹จ์ง€ ํ•œ๋ฒˆ๋„ ์ „์ฒด์ ์ธ ๊ฐœ๋ฐœ ๊ฒฝํ—˜์„ ๋ชปํ•œ๋ถ„๋“ค์„ ์œ„ํ•œ ๊ฒƒ์ด๋ฏ€๋กœ, ์™„๋ฒฝํ•œ ์†Œ์Šค ๋‚ด์šฉ์ด ์•„๋‹˜์„ ๋‹ค์‹œ ํ•œ๋ฒˆ ๋ง์”€๋“œ๋ฆฌ๋ฉฐ, ์ œ 1ํŽธ์„ ๋งˆ์น˜๊ณ ์ž ํ•œ๋‹ค.

 

 ๋งํฌ์„œ๋น„์Šค git : https://github.com/firstvalue1/LINKSERVICE.git

 

GitHub - firstvalue1/LINKSERVICE

Contribute to firstvalue1/LINKSERVICE development by creating an account on GitHub.

github.com

๋ณด์‹œ๋‹ค ์ˆ˜์ •์ด๋‚˜, ์˜๊ฒฌ ์žˆ์œผ์‹œ๋ฉด git์— ๋‚จ๊ฒจ์ฃผ์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

 

 

๋‹ค์Œ์€ ๋งํฌ์„œ๋น„์Šค๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ์ถ”๊ฐ€์ ์ธ ๊ธฐ๋Šฅ๋“ค์„ ๊ณ„์† ๊ฒŒ์‹œ ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

 

#15. ๋งํฌ์„œ๋น„์Šค ๊ด€๋ฆฌ์žํ™”๋ฉด ๋ฐ ๋งํฌ๋ฆฌ์ŠคํŠธ ํŽธ์ง‘

 

#15. ๋งํฌ์„œ๋น„์Šค ๊ด€๋ฆฌ์žํ™”๋ฉด ๋ฐ ๋งํฌ๋ฆฌ์ŠคํŠธ ํŽธ์ง‘

โŽŸโŽœ๊ด€๋ฆฌ์ž ํ™”๋ฉด ์ œ์ž‘ โŒ˜ manager.jsx ( ์œ„์น˜: linkservice/client/src/component/manager.jsx ) -------------------------------------------------------------------------------------------------- CSS ์„ ๋จผ์ € ์ถ”๊ฐ€ํ•œ๋‹ค. ( links.css ํŒŒ์ผ์— ์ถ”

firstvalue.tistory.com

์šฐ์„  ๋งํฌ์ •๋ณด๋ฅผ ํŽธ์ง‘ํ•˜๋ฉด์„œ ๋งํฌ์ •๋ณด๋ฅผ ์‚ญ์ œ์ฒ˜๋ฆฌ์‹œ ์ด๋ฏธ ๋“ฑ๋ก๋œ ์ด๋ฏธ์ง€๋ฅผ ์‚ญ์ œํ•˜๋Š” ๋ฐฑ์—”๋“œ๋ฅผ ์ œ์ž‘ํ•˜์ž

 

โŒ˜ links.py ( ์œ„์น˜: linkservice/server/src/links.py)

---------------------------------------------------------------------------------------------------

Python๋ฐฑ์—”๋“œ links.py์˜ LinkRemove(Resource) ์„ ์ˆ˜์ •ํ•œ๋‹ค.

๊ด€๋ จ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ import ํ•œ๋‹ค. ( current_app, os )

from flask import jsonify, request, current_app
import os
"""
DELETE /api/link/delete/<int:id> - ๋งํฌ์ •๋ณด์‚ญ์ œ
"""
class LinkRemove(Resource):
    def delete(self, id):
        # id์— ํ•ด๋‹น๋˜๋Š” ๋งํฌ ์ •๋ณด ์กฐํšŒ
        link_data = LinkModel.find_by_id(id)
        folder = current_app.config.get('IMAGE_FORDER')
        # ์ด๋ฏธ์ง€ ๋งํฌ ํ•ญ๋ชฉ ๊ฐ€์ ธ์˜ค๊ธฐ
        if link_data.imageurl is not None:
            imageurl = link_data.imageurl.split('/')
            file_name = imageurl[len(imageurl) - 1]
            #ํŒŒ์ผ ์กด์žฌ์—ฌ๋ถ€ ํ™•์ธํ›„ ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ์‚ญ์ œํ•œ๋‹ค.
            if os.path.isfile(folder + file_name):
                os.remove(folder + file_name)

        # ์ •๋ณด ์‚ญ์ œ ( links_model์˜ delete_by_id ๋ฉ”์†Œ๋“œ ํ˜ธ์ถœ )
        LinkModel.delete_by_id(id)

        return {'message':'์ •์ƒ์ ์œผ๋กœ ์‚ญ์ œ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'},201

โŽŸโŽœ๋งํฌ์ •๋ณด๋ฅผ ํŽธ์ง‘ํ•˜๋Š” ํ™”๋ฉด ์ œ์ž‘

 

โŒ˜ links.edit.jsx ( ์œ„์น˜: linkservice/client/src/component/links.edit.jsx )

-------------------------------------------------------------------------------------------------

links.edit.jsx๋Š” ๋งํฌ ์ •๋ณด๋ฅผ ํŽธ์ง‘ ํ•˜๋Š” ํ™”๋ฉด์ด๋‹ค.

ํ™”๋ฉด ๊ตฌ์„ฑ๊ณผ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜๋“ค์€ ๋ชจ๋‘ ์‹ ๊ทœ ๋“ฑ๋ก(links.create.jsx) ๊ณผ ๋™์ผํ•˜๋ฉฐ,

 

๋‹จ useEffect์—์„œ ํŽธ์ง‘ํ•˜๊ณ ์ž ํ•˜๋Š” ๋งํฌ์ •๋ณด์˜ id ๊ฐ’์„ ๋ฐ›์•„ ์ถœ๋ ฅํ•˜๋Š” ๋ถ€๋ถ„๊ณผ handleSubmit์— ์—…๋ฐ์ดํŠธํ•˜๋Š”

์„œ๋น„์Šค๋ฅผ ์ถ”๊ฐ€ ํ•˜๋ฉด ๋œ๋‹ค.

 

import React, { useState, useEffect } from "react"
import { useParams, useNavigate } from "react-router-dom"
import linkService from "./service/service";
import {useDropzone} from 'react-dropzone'

export default function LinkEdit () {

    //id๊ฐ’์„ ๋ฐ›์•„์˜ค๊ธฐ ์œ„ํ•ด์„œ react์—์„œ ์ œ๊ณตํ•œ useParams์„ ํ™œ์šฉํ•œ๋‹ค.
    let params = useParams()
    const [data, setData] = useState({})
    const [values, setValues] = useState({})
    const [image, setImage] = useState(null)
    const [preview, setPreview] = useState('/assets/default.jpg')
    const navigate = useNavigate();

      useEffect(() => {
        linkService.getSelectLink(params.id).then(
            (res)=> {
            setData(res.data)
             // ์ˆ˜์ • ์„œ๋น„์Šค์ธ linkService.updateLink ์— ๋„˜๊ฒจ์ฃผ๊ธฐ ์œ„ํ•œ values            
            setValues(res.data.links)
            // preview์„ ์œ„ํ•œ setProview๊ฐ’ 
            setPreview(res.data.links.imageurl)
          }, (error) => {
            alert(error.res.data.message )
          }
          )
        // eslint-disable-next-line 
      }, [params.id])

      // Drag & Drap ๊ด€๋ จ ์‹คํ–‰ function 
      function onDrop(acceptedFiles) {
        const reader = new FileReader();
        const file = acceptedFiles
        if (file) {
          reader.readAsDataURL(file[0]);
          setImage(file[0]);
        }
        reader.onload = (e) => {
            setPreview(reader.result);
            setValues((prevValues) => ({
                ...prevValues,
                [`imageurl`]: file[0].name,
            }))
            document.getElementsByName("imageurl")[0].value = ''
        };
    }

    const handleChange = async (e) => {
        const nextValues = {
            ...values,
            [e.target.name]: e.target.value
        }
        setValues(nextValues)
        // ์‚ฌ์ง„๋งํฌ์ •๋ณด ์ž…๋ ฅ์‹œ preview๋ฅผ ์œ„ํ•œ set
        if (e.target.name === 'imagelink') {
            setPreview(e.target.value)
        }
    }

    const handleSubmit = async (e) => {
        e.preventDefault();
        const formData = new FormData();
        linkService.updateLink(params.id, values).then(
            (res)=> {
            // image file์„ ์„ ํƒ์‹œ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ๋ฅผ ์œ„ํ•ด์„œ ์ง„ํ–‰
            if (image) {
                formData.append('image', image)
                linkService.changeImage(formData, params.id).then(
                    (res)=> {
                    // console.log(res)
                    }
                )
            }
            alert(res.data.message)
            navigate("/links/list", { replace: true})

         }, (error) => {
            alert(error.res.data.message )
         }
        )
    }
    // Drag & Drop์„ ์œ„ํ•œ ๊ธฐ์กด const ์„ ์–ธ
    const {getRootProps, getInputProps} = useDropzone({onDrop})

    return (
        <div>
      {(typeof data.links ==='undefined') ? (
        <p>Loading....</p>
      ) : (
        <>            
            <div className="title-container">
                <h1 className="text-title">๋งํฌ ์ˆ˜์ •</h1>
            </div>

            <form  onSubmit={handleSubmit}>                            
                <div className="input-wrap">
                    <label>๋งํฌ๋ช…</label>
                    <div className="input-box">
                        <input type="text" name="name" 
                            defaultValue={data.links.name} 
                            className="input-css"
                            onChange={handleChange}
                            autoFocus="autofocus"
                        />  
                    </div> 
                </div>
                <div className="input-wrap">
                    <label>ํƒœ๊ทธ ์ •๋ณด ( ํƒœ๊ทธ์—ฌ๋Ÿฌ๊ฐœ ๋“ฑ๋ก์‹œ / ๋กœ ๊ตฌ๋ถ„ )</label>
                    <div className="input-box">
                        <input type="text" name="tag"
                            defaultValue={data.links.tag} 
                            className="input-css"
                            onChange={handleChange}
                        />
                    </div>
                </div>
                <div className="input-wrap">
                    <label>๋งํฌ URL (์—ฐ๊ฒฐํ•˜๊ณ ์ž ํ•˜๋Š” URL)</label>
                    <div className="input-box">                                 
                    <input type="text" name="linkurl" 
                        defaultValue={data.links.linkurl} 
                        className="input-css"
                        onChange={handleChange}
                        />    
                    </div>                                 
                </div>
                <div className="input-wrap">
                    <label>์‚ฌ์ง„ ๋งํฌ ์ •๋ณด</label>
                    <div className="input-box">                                 
                    <input type="text" name="imageurl" 
                        defaultValue={data.links.imageurl} 
                        className="input-css"
                        onChange={handleChange}
                        />     
                    </div>                                  
                </div>   
                <div className="img_contain" >
                    <div className="img_wrap" {...getRootProps()} > 
                        <img src =  { preview }
                            alt='์ด๋ฏธ์ง€' className="img_box" />
                        <input {...getInputProps()} multiple={false} name='imageurl'/>
                    </div> 
                        <div className="text_type">
                        <span>์‚ฌ์ง„ ๋งํฌ ์ •๋ณด๋ฅผ ๋“ฑ๋กํ•˜๊ฑฐ๋‚˜ <br></br>
                        ์ด๋ฏธ์ง€๋ฅผ ์ง์ ‘ ๋Œ์–ด์˜ค๊ฑฐ๋‚˜<br></br>
                        ํŒŒ์ผ์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”</span>
                        </div>

                </div> 
                <div>
                    <button type="submit" className="btn btn-primary">์ˆ˜ ์ •</button>
                </div>
            </form>
            </>
         )}                          
        </div>
    )
}

๋งํฌ์ •๋ณด์— ๋Œ€ํ•œ ๋“ฑ๋ก/์ˆ˜์ •/์‚ญ์ œ๊ฐ€ ๋ชจ๋‘ ๋๋‚ฌ๋‹ค.

 

๋‹ค์ŒํŽธ์—๋Š” ๋งŒ๋“ค์–ด์ง„ ์†Œ์Šค๋กœ ์ข€๋” ํ•„์š”ํ•œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๊ธฐ๋Šฅ๊ณผ ๋””ํ…Œ์ผํ•œ ์ž‘์—…์„ ์ง„ํ–‰ํ•ด ๋ณด์ž.

โŽŸโŽœ๊ด€๋ฆฌ์ž ํ™”๋ฉด ์ œ์ž‘

 โŒ˜ manager.jsx ( ์œ„์น˜: linkservice/client/src/component/manager.jsx )

--------------------------------------------------------------------------------------------------

CSS ์„ ๋จผ์ € ์ถ”๊ฐ€ํ•œ๋‹ค. ( links.css ํŒŒ์ผ์— ์ถ”๊ฐ€ )

/* ๋งํฌ ์Šคํƒ€์ผ์„ ์—†์• ์ฃผ๋Š”๊ฒƒ  */
.a-tag {
    color: inherit;
    text-decoration: inherit;  
width: 100%; 
}
.manager-button {
    border-radius: 8px;
    border: none;
    margin: 0;
    width: 100%;    
    margin-bottom: 10px;
    height: 50px;
    padding: 0 20px;
    font-size: 18px;
    background-color: #8090F5;
    color: #fff;
}

manager.jsx ์— ManagerHome() ์„ ์ƒ์„ฑํ•œ๋‹ค.

import React from "react"
import { Link } from "react-router-dom"

export default function ManagerHome() {

    return (
<>
        <Link to="/links/list">
        <button type="button" className="manager-button">
            <div> ๋งํฌ ์ •๋ณด ๊ด€๋ฆฌ </div>
        </button>
        </Link>    

        <Link to="#" >
        <button type="button" className="manager-button">
            <div> ์Šค์ผ€์ค„ ์ •๋ณด ๊ด€๋ฆฌ </div>
        </button>
        </Link>  
  </>
    )
}

App.js์— Route์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

App.js

โŽŸโŽœ๋งํฌ์ •๋ณด ๊ด€๋ฆฌํ•˜๋Š” ๋ฆฌ์ŠคํŠธ ํ™”๋ฉด ์ œ์ž‘

 

โŒ˜ links.list.jsx ( ์œ„์น˜: linkservice/client/src/component/links.list.jsx )

-------------------------------------------------------------------------------------------------

links.list.jsx๋Š” ๋งํฌ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ , ์‚ญ์ œ๊ธฐ๋Šฅ๊ณผ ์‹ ๊ทœ๋“ฑ๋ก/ํŽธ์ง‘ ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•˜๊ธฐ ์œ„ํ•œ ํ™”๋ฉด์ด๋‹ค.

 

ํ™”๋ฉด์€ links.jsx์™€ ๋น„์Šทํ•˜๊ณ , ์‚ญ์ œ ๋ฒ„ํŠผ๊ณผ ๋‚ด์šฉ์„ ํด๋ฆญํ•˜๋ฉด /link/edit/{id} ๋กœ ๋„˜๊ธฐ๋ฉด ๋œ๋‹ค.

 

์‚ญ์ œ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ handleDelete(id)๋Š” linkService์˜ deleteLink(id) ์„ ํ˜ธ์ถœํ•˜๋ฉด ๋œ๋‹ค.

         โ˜ž navigate(0)๋Š” ํ˜„์žฌ ํŽ˜์ด์ง€๋ฅผ reloadํ•œ๋‹ค.

import React, { useState, useEffect } from "react";
// mui Icon์—์„œ Delete ๋ฒ„ํŠผ์„ importํ•œ๋‹ค.
import { DeleteOutline } from "@mui/icons-material";
import { Link , useNavigate } from 'react-router-dom'
import linkService from "./service/service";

export default function LinksList () {
    const [data, setData] = useState([{}])  
    const navigate = useNavigate();
// ๋งํฌ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ „์ฒด ๋ชฉ๋ก์„ ๋ถˆ๋ ค์˜ค๋Š” getLinks์„ ํ†ตํ•ด ๋ฐ›์•„ ์˜จ๋‹ค.
    useEffect(() => {
        linkService.getLinks().then(
            (res)=> {
            setData(res.data.links)
            console.log(res.data.links)
          }, (error) => {
            alert(error.res.data.message )
          }
          )
    }, [])
// ์‚ญ์ œ ๋ฒ„ํŠผ์„ ํด๋ฆญ์‹œ ์‚ญ์ œํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
// param : id
    const handleDelete = (id) => {
        // e.preventDefault();
        linkService.deleteLink(id).then(
            (res)=> {
              alert(res.data.message)
              navigate(0)
          }, (error) => {
            alert(error.res.data.message )
          }
          )
      }
    return (
<>
        <div>
            <Link to = "/create">
                <button type="submit" className="manager-button">+ ์‹ ๊ทœ ๋งํฌ ์ •๋ณด ๋“ฑ๋ก</button>
            </Link>
        </div>

        {(typeof data ==='undefined') ? (
            <p>Loading....</p>
            ) : (
            data.map((link, index) => (   
            <div className="link-container" key={index} >
                <div className="link-wrapper">
                    <div className="link-show">
                        <img
                        src={link.imageurl}
                        alt=""
                        className="link-img"
                        />
                      <Link to={'/link/edit/' + link.id} className="a-tag">
                        <div className="list-title"> {link.name }</div> 
                      </Link>

                      <div className="delete-icon" name='deleteid'>                                                                              
                        <DeleteOutline fontSize='large' color="disabled"
                            onClick={() => handleDelete(link.id)}/>                               
                      </div>

                    </div>     
                </div>
                
            </div>
             ))
            )}    
</>
    )
}

์‚ญ์ œ ์ด๋ฒคํŠธ์ธ handleDelete๋Š” API ํ•จ์ˆ˜๊ฐ€ ๋งํฌ์ •๋ณด๋งŒ ์‚ญ์ œํ•œ๋‹ค.

 

ํ•˜์ง€๋งŒ, ์ด๋ฏธ์ง€๋ฅผ ํŒŒ์ผ ํ˜•ํƒœ๋กœ ๋“ฑ๋ก์ด ๋˜๋ฏ€๋กœ, ๋งํฌ์ •๋ณด ์‚ญ์ œ๊ฐ€ ๋˜๋ฉด ์ด๋ฏธ์ง€ ํŒŒ์ผ๋„ ์‚ญ์ œ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผ ํ•œ๋‹ค.

 

์ด๋ฏธ ๋‹ค์šด๋กœ๋“œ๋œ ์ด๋ฏธ์ง€๋ฅผ ์‚ญ์ œ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฑ์—”๋“œ๋Š” ๋‹ค์ŒํŽธ์—.....

 

๋“ฑ๋กํ™”๋ฉด์€ ์™„๋ฃŒ๋˜์—ˆ์œผ๋‹ˆ, ์ด์   ํŽธ์ง‘ํ™”๋ฉด์„ ๊ฐœ๋ฐœํ•ด ๋ณด์ž.

#13. ๋“ฑ๋กํ™”๋ฉด์—์„œ ๋‚ด์šฉ ์ €์žฅ ๋ฐ ์ด๋ฏธ์ง€ ์ €์žฅ ๊ธฐ๋Šฅ ๊ตฌํ˜„

 

#13. ๋“ฑ๋กํ™”๋ฉด์—์„œ ๋‚ด์šฉ ์ €์žฅ ๋ฐ ์ด๋ฏธ์ง€ ์ €์žฅ ๊ธฐ๋Šฅ ๊ตฌํ˜„

#12. React์—์„œ Drag & Drop์„ ์ด์šฉํ•œ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ #12. React์—์„œ Drag & Drop์„ ์ด์šฉํ•œ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋งํฌ์„œ๋น„์Šค ๋“ฑ๋กํ™”๋ฉด์—์„œ Input Value์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•˜๋‹ค. ๊ทธ๋Ÿผ ์ด๋ฏธ์ง€ ๋“ฑ๋ก์„ ์œ„ํ•œ Drag&Drop ์ด

firstvalue.tistory.com

 

 

ํŽธ์ง‘ํ™”๋ฉด์„ ๊ฐœ๋ฐœํ•˜๊ธฐ์ „์— ๊ฐœ๊ฐœ ๋ณ„ ํ™”๋ฉด์„ ์ œ์ž‘ํ•˜๋‹ค ๋ณด๋‹ˆ, ํ™”๋ฉด ํ๋ฆ„์ด ๋˜๋„๋ก ๊ฐœ๋ฐœ์„ ์ถ”๊ฐ€ ํ•œ๋‹ค.

ํ™”๋ฉด ํ๋ฆ„์€ ๋Œ€๋žต์ ์œผ๋กœ ์•„๋ž˜ ์‹์œผ๋กœ ๊ตฌํ˜„์„ ํ•œ๋‹ค.

 

๊ด€๋ฆฌ์ž๋ชจ๋“œ ํ™”๋ฉด์„ ๋งŒ๋“ค์–ด ๋“ฑ๋ก/ํŽธ์ง‘/์‚ญ์ œ/์ˆœ์„œ๋ณ€๊ฒฝ์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ Prototype UI์„ ๊ตฌ์„ฑํ•ด ๋ณด์ž.

 

โŽŸโŽœ์ดˆ ํ™”๋ฉด ํ•˜๋‹จ์— ๋ฉ”๋‰ด ์ œ์ž‘

 

โŒ˜ menu.jsx ( ์œ„์น˜: linkservice/client/src/component/menu.jsx )

---------------------------------------------------------------------------------------------------

React์—์„œ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” Material UI Icon ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”๋‰ด๋“ค์„ ์ƒ์„ฑํ•ด ๋ณด์ž.

 

ํ„ฐ๋ฏธ๋„์„ ์—ด์–ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•œ๋‹ค.

ใ€‰ใ€‰ yarn add @mui/icons-material @mui/material @emotion/styled @emotion/react

 

์•„์ด์ฝ˜์€ https://mui.com/material-ui/material-icons/ ์—ฌ๊ธฐ์„œ ์ฐพ์•„ ์ ์šฉํ•œ๋‹ค.

์šฐ์„  HOME ๊ณผ ๊ด€๋ฆฌ์ž(SETTING) ์•„์ด์ฝ˜์„ ์ฐพ์•„ ๋ง˜์— ๋“œ๋Š” ์•„์ด์ฝ˜์„ ํด๋ฆญํ•˜๋ฉด import ๋‚ด์šฉ์ด ๋‚˜์˜จ๋‹ค. ๋ณต์‚ฌํ•˜์—ฌ ๋ถ™์ธ๋‹ค.

 

material-icons

import HomeIcon from '@mui/icons-material/Home';
import SettingsIcon from '@mui/icons-material/Settings';

export default function MenuIcon(props) {

    return (
<>
      <div className='menu-wrap'>
        <div className='menu-box'>
            <HomeIcon fontSize='large'  color="secondary"/>
            <SettingsIcon fontSize='large'  color="secondary" /> 
        </div>
      </div>      
</>
    )

CSS ์„ ์ถ”๊ฐ€ํ•œ๋‹ค. ( links.css ํŒŒ์ผ์— ์ถ”๊ฐ€ )

.menu-wrap {
    display: flex;    
    margin: 0 auto;
    justify-content: center;
}
.menu-box {
    padding: 20px;
    text-align: center;
}

Material Icon์— ์ƒ‰์ƒ๊ณผ ํฌ๊ธฐ๋ฅผ ์ ์šฉ ํ–ˆ๋‹ค.

Icon ํด๋ฆญ ์‹œ ํ•ด๋‹น ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋Š” onClick()์˜ HandleIcon() ํ•จ์ˆ˜๋ฅผ ์ œ์ž‘ํ•œ๋‹ค.

import HomeIcon from '@mui/icons-material/Home';
import SettingsIcon from '@mui/icons-material/Settings';
import { useNavigate } from 'react-router-dom';

export default function MenuIcon(props) {
    const navigator = useNavigate()
    const handleIcon = (e) => {
        switch (e) {
            case 'A' : return navigator('/manager')
            case 'M' : return navigator('/')
            default : return navigator('/')
        }
    }
    return (
<>
      <div className='menu-wrap'>
        <div className='menu-box'>
            <HomeIcon fontSize='large' color="secondary"
                onClick={() => handleIcon('M')} 
            />
            <SettingsIcon fontSize='large' color="secondary"
                onClick={() => handleIcon('A')} 
            /> 
        </div>
      </div>      
</>
    )
}

๋ชจ๋“  ํŽ˜์ด์ง€์— ๋ฉ”๋‰ด๊ฐ€ ๋ณด์ด๋„๋ก App.js์„ ์ˆ˜์ •ํ•œ๋‹ค.

App.js

 

 

#12. React์—์„œ Drag & Drop์„ ์ด์šฉํ•œ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ

 

#12. React์—์„œ Drag & Drop์„ ์ด์šฉํ•œ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ

๋งํฌ์„œ๋น„์Šค ๋“ฑ๋กํ™”๋ฉด์—์„œ Input Value์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•˜๋‹ค. ๊ทธ๋Ÿผ ์ด๋ฏธ์ง€ ๋“ฑ๋ก์„ ์œ„ํ•œ Drag&Drop ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ๋ฅผ ์ œ์ž‘ํ•ด๋ณด์ž. #11. Reactํ™”๋ฉด์—์„œ ๊ฐ ํ•ญ๋ชฉ์˜ Input Value์„ ๊ฐ€์ ธ์˜ค๊ธฐ #11. Reactํ™”๋ฉด์—์„œ

firstvalue.tistory.com

 

React์—์„œ ๋“ฑ๋กํ™”๋ฉด์—์„œ ์ด๋ฏธ์ง€๋ฅผ Drag&Drop ํ• ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„์„ ํ–ˆ๋‹ค.

๊ทธ๋Ÿผ ์ด์   ๋“ฑ๋กํ™”๋ฉด์˜ ๋งˆ๋ฌด๋ฆฌ๋กœ ์ €์žฅ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด๋ณด์ž.

 

 

1.1.  ์ด๋ฏธ์ง€ ์ €์žฅ๊ธฐ๋Šฅ ์ œ์ž‘

์ด๋ฏธ์ง€ ์ €์žฅ๊ธฐ๋Šฅ(linkSevice.uploadImage) ์€ ์•„์ง API ์ œ์ž‘ ๋ฐ Service์„ ์–ธ์„ ํ•˜์ง€ ์•Š์•˜๋‹ค.

     โ˜ž ์„ค๋ช…์ฐจ์›์—์„œ ์„œ๋ฒ„๋‹จ์— API์ œ์ž‘ ์‹œ ์ œ์™ธ ํ–ˆ๋‹ค. ์•ž๋‹จ์—์„œ ๋ญ” ์ง€ ๋ชจ๋ฅธ ์ƒํƒœ์—์„œ ์†Œ์Šค๋ฅผ ๋ฏธ๋ฆฌ ์ œ์ž‘ํ•˜์ง€ ์•Š๊ณ ,

          ์ดํ•ด๋ฅผ ๋•๊ธฐ ์œ„ํ•ด ์ง€๊ธˆ ๋‚ด์šฉ์„ ๋ฐ˜์˜ํ–ˆ๋‹ค.

 

๋งํฌ์ •๋ณด๋ฅผ DB์— ์ €์žฅ ์‹œ ์šฐ๋ฆฌ๋Š” ๋ฏธ๋ฆฌ /api/link/create API ์„ ์‚ฌ์ „์— ์ œ์ž‘ํ–ˆ๋‹ค.

ํ•˜์ง€๋งŒ, ์‹ค์ œ ํ™”๋ฉด์—์„œ DB ์ €์žฅ ์‹œ 2๊ฐ€์ง€๊ฐ€ ๋” ์ถ”๊ฐ€๋˜์–ด์•ผ ํ•œ๋‹ค.

 

์ฒซ์งธ, ์ด๋ฏธ์ง€ ํŒŒ์ผ ์ž์ฒด๋ฅผ ์ €์žฅํ•˜๋Š” ๊ธฐ๋Šฅ

๋‘˜์งธ, ์ด๋ฏธ์ง€ ํŒŒ์ผ๋ช…(URL)์„ ๋งํฌ์ •๋ณด DB์— ์ €์žฅํ•˜๋Š” ๊ธฐ๋Šฅ

 

โŽŸโŽœ์ด๋ฏธ์ง€ ์ €์žฅ ๊ธฐ๋Šฅ

 

์ด๋ฏธ์ง€ ์—…๋กœ๋“œ๋Š” ๋ณดํ†ต DB์— ์ง์ ‘ ๋„ฃ๋Š” ๋ฐฉ๋ฒ•๊ณผ ์„œ๋ฒ„์— ์ด๋ฏธ์ง€๋ฅผ ๋ณด๊ด€ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค. ์žฅ๋‹จ์ ์€ ์žˆ์œผ๋‚˜, ์ด๋ฏธ์ง€๋ฅผ DB์— ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์€ ํ–ฅํ›„ ์ด๋ฏธ์ง€ ํ˜ธ์ถœ ์‹œ ์†๋„์™€ DB์šฉ๋Ÿ‰๊ณผ ์œ ์ง€๋ณด์ˆ˜์— ๋ถˆํŽธํ•จ์ด ์žˆ์–ด, ๋ณ„๋„ ์„œ๋ฒ„์— ๋ณด๊ด€ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ง„ํ–‰ํ•œ๋‹ค.

 

๊ธฐ์—…์˜ ๊ฐœ๋ฐœ์€ CDN์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ๋ณ„๋„ ๋ณด๊ด€ ๋ฐฉ์‹์„ ์ถ”์ฒœํ•œ๋‹ค.

์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์ œ์ž‘ํ•˜๋ฉด์„œ, ๋‹ค์‹œํ•œ๋ฒˆ ์„œ๋ฒ„๋‹จ๊ณผ ํด๋ผ์ด์–ธํŠธ๋‹จ์˜ API์ œ์ž‘ ๊ด€๋ จํ•ด์„œ ์ •๋ฆฌํ•ด๋ณด์ž.

 

1)   ์„œ๋ฒ„ RESTful API์ œ์ž‘

- ์ด๋ฏธ์ง€ ๋ณด๊ด€ ํด๋” ์ง€์ • ( config.py )

- ์ด๋ฏธ์ง€ ์—…๋กœ๋“œํ›„ ์ด๋ฏธ์ง€ ํŒŒ์ผ ์ด๋ฆ„๊ณผ URL์„ DB์— ๋ณด๊ด€

- ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ / ์‚ญ์ œ CLASS ์ œ์ž‘

- API ์„ ์–ธ

 

2) ํด๋ผ์ด์–ธํŠธ API ํ˜ธ์ถœ

   - API config์— ์„œ๋ฒ„๋‹จ API ํ˜ธ์ถœ url๊ด€๋ฆฌ ( api.config.js )

- ์„œ๋น„์Šค์— API ํ˜ธ์ถœ const ์ง€์ • ( service.js )

 

โŽŸโŽœ์„œ๋ฒ„๋‹จ API ์ œ์ž‘

 

โŒ˜ config.py ( ์œ„์น˜: linkservice/server/config/config.py )

--------------------------------------------------------------------------------------------------

์•ž์—์„œ config.py์— ๊ฐœ๋ฐœ๊ณ„(Development)์™€ ์šด์˜๊ณ„(Production) ์ด๋ฏธ์ง€ ์ €์žฅ ์œ„์น˜๋ฅผ ์ง€์ • ํ–ˆ์—ˆ๋‹ค.

#์ด๋ฏธ์ง€ ์œ„์น˜ ์„ค์ •
    image_folder = os.path.normpath(os.path.join(dir, os.pardir)) + '/client/public/images/'
    COVER_IMAGE_FORDER = image_folder
    IMAGE_FORDER = image_folder
    IMAGE_URL = 'http://127.0.0.1:3000/images/'

image_folder๋Š” ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ์˜ ๋ถ€๋ชจ ๋””๋ ‰ํ† ๋ฆฌ ( /linkservice) ์„ ํ™•์ธํ•ด์„œ /client/public/images ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๊ฐ€๋ฆฌํ‚จ๋‹ค.

( ํ•ด๋‹น ํด๋”์— images ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋งŒ๋“ค์ž )

โ˜ž NAS๋‚˜ CDN ์ €์žฅ ์‹œ image_folder์— ์œ„์น˜๋ฅผ ์ง€์ •ํ•˜๋ฉด ๋œ๋‹ค.
โ˜ž COVER_IMAGE_FORDER
๋Š” ํ–ฅํ›„ ์ปค๋ฒ„ ์ด๋ฏธ์ง€๋ฅผ ์ถ”๊ฐ€/๋ณ€๊ฒฝ ํ•  ์˜ˆ์ •์ด๋ผ ๋ฏธ๋ฆฌ ์„ค์ •ํ–ˆ๋‹ค.

 

โŒ˜ links_model.py ( ์œ„์น˜: linkservice/server/models/links_model.py )

--------------------------------------------------------------------------------------------------

 

๊ธฐ์กด์— links_model.py์— 2๊ฐ€์ง€๊ฐ€ ์ถ”๊ฐ€์ ์œผ๋กœ ํ•„์š”ํ•˜๋‹ค.

์ด๋ฏธ์ง€ ์—…๋กœ๋“œํ›„ ๋งํฌ์ •๋ณด์— ์ด๋ฏธ์ง€ ์ด๋ฆ„์„ ๋งคํ•‘ ์‹œํ‚ค๊ธฐ ์œ„ํ•ด์„œ ์ด๋ฏธ์ง€ ์ด๋ฆ„์„ ์ €์žฅํ•˜๋Š” method์™€ ํ˜„์žฌ DB์˜  id ์‹œํ€€์Šค ๊ฐ’์„ ์•Œ์•„๋‚ด๋Š” method๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

 

id์˜ ํ˜„์žฌ ์‹œํ€€์Šค๋ฅผ ์•Œ์•„์•ผ ํ•˜๋Š” ์ด์œ ๋Š” ์ €์žฅ์ˆœ์„œ๋ฅผ ์ž˜ ์ƒ๊ฐํ•˜๋ฉด ํ•„์š”์„ฑ์ด ๋ณด์ธ๋‹ค.

์ด๋ฏธ์ง€๊ฐ€ ๋จผ์ € ์ €์žฅ ๋˜๋ฉด id์„ ์•Œ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ, 1)๋งํฌ์ •๋ณด(์ด๋ฆ„,ํƒœ๊ทธ ๋“ฑ)๊ฐ€ ๋จผ์ € ์ €์žฅ๋˜์–ด ์‹œํ€€์Šค์ธ id๊ฐ’์ด ์ƒ์„ฑํ•œ ๋‹ค์Œ 2)์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•ด์•ผ ํ•œ๋‹ค.

from sqlalchemy.sql import text

query์— raw string ํ˜•ํƒœ์˜ SQL๋ฌธ์„ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด์„œ sqlalchemy.sql.text ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

์šฐ์„  sqlalchemy.sql.text ์„ import ํ•˜๊ณ , ์•„๋ž˜ ์†Œ์Šค๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

# ๋งํฌ ์ด๋ฏธ์ง€ ์ •๋ณด๋ฅผ DB์— ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ์ฝ”๋“œ
# id์™€ ํŒŒ์ผ์ด๋ฆ„ ํ˜น์€ ์ด๋ฏธ์ง€ ๋งํฌ๋ฅผ ํŒŒ๋ผ๋ฉ”ํƒ€๋ฅผ ๋ฐ›์•„ ์ฒ˜๋ฆฌ
    @classmethod
    def update_filename(self, id, filename):
        try:
            query = "update links set imageurl = :v2 where id = :v1"
            db.session.execute(text(query), {'v1':id, 'v2':filename})
        except:
            db.session.rollback()
            print('update_filename : ์˜ค๋ฅ˜๋ฐœ์ƒ')
            print(query)
        finally:
            db.session.commit()
            db.session.close()

# ๋งํฌ์ •๋ณด๊ฐ€ ์ €์žฅ๋œ ํ˜„์žฌ ์‹œํ€€์Šค id ๊ฐ’์„ ์•Œ์•„๋‚ธ๋‹ค. 
	@classmethod
	def currval_id(self):
		result = db.session.query(func.max(self.id)).scalar()
		return result

โŒ˜ image.py ( ์œ„์น˜: linkservice/server/src/image.py )

-------------------------------------------------------------------------------------------------

์ด๋ฏธ์ง€ ๊ด€๋ จ Resource์„ ์ œ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด์„œ image.py์„ ํ•˜๋‚˜ ์ƒ์„ฑํ•œ๋‹ค.

image.py์—์„œ๋Š” ์ธ๋„ค์ผ ์ •๋„์˜ ์ด๋ฏธ์ง€๋งŒ ์žˆ์œผ๋ฉด ๋˜๋ฏ€๋กœ, 1) ์ด๋ฏธ์ง€๋ฅผ resizing์„ ํ•˜๊ณ , 2)์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ํด๋”์— ์ €์žฅํ•œ ๋‹ค์Œ, 3) ๋งํฌ์ •๋ณด DB์— ์ด๋ฏธ์ง€ ํŒŒ์ผ๋ช…(URL)์„ ๋งคํ•‘ ์‹œํ‚จ๋‹ค.

โ˜ž DB์— ์ €์žฅ๋˜๋Š” ์ด๋ฏธ์ง€ ํŒŒ์ผ๋ช…์€ ์‹ค์ œ CDNํ˜น์€ NAS ์˜ URLํ˜•ํƒœ๋กœ ์ €์žฅ๋œ๋‹ค.

   config.py์˜ IMAGE_URL ๊ฐ’์„ ์ฐธ๊ณ ํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

from flask_restful import Resource
from flask import jsonify, request, current_app
from PIL import Image, ImageOps
from models.links_model import LinkModel

"""
UPLOAD  IMAGE /api/link/image 
"""
class ImageUpload(Resource):
    def post(self):
        f = request.files['image']
        folder = current_app.config.get('IMAGE_FORDER')
        img_url = current_app.config.get('IMAGE_URL')

        img = Image.open(f)
        
        # ์ด๋ฏธ์ง€๋ฅผ ๊ฐ•์ œ์ ์œผ๋กœ Resizingํ•œ๋‹ค.
        # Resizing์„ ์•ˆํ•ด๋„ ์ข‹์œผ๋‚˜, ํŒŒ์ผ ์šฉ๋Ÿ‰์„ ์ค„์ด๊ณ ์ž ๋ฐ˜์˜
        img_resize = img.resize((50,50), Image.LANCZOS)
        img_resize = ImageOps.exif_transpose(img_resize)
        img_resize = img_resize.convert('RGB')

        id = LinkModel.currval_id()
        file_name = str(id) + '_' + str(date_function.today()) + '.' + f.filename.rsplit('.', 1)[1].lower()
        img_resize.save(folder + file_name)
		LinkModel.update_filename(id, img_url + file_name)

        return '์ด๋ฏธ์ง€๊ฐ€ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค..'

ํ”„๋ก ํŠธ์—”๋“œ์—์„œ formData๋กœ ๋ณด๋‚ธ image์„ request.files๋กœ ๋ฐ›์•„ Python์˜ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ pillow(PIL) ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฏธ์ง€๋ฅผ Resizingํ•œ๋‹ค.

 

๋˜ํ•œ ํŒŒ์ผ ์ด๋ฆ„์„ ๋ณ€๊ฒฝํ•œ๋‹ค. ์ด์œ ๋Š” ํ•œ๊ธ€ ํ˜น์€ ๊ฐ™์€ ํŒŒ์ผ ์ด๋ฆ„์ด ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ์— ๋Œ€๋ถ€๋ถ„ ์‚ฌ์ดํŠธ๋ฅผ ๋ณด๋ฉด ๋ช…๋ช…๊ทœ์น™(Naming rule)์— ๋งž๊ฒŒ ๋ณ€๊ฒฝํ•˜์—ฌ ์ž์ฒด ๋ณด๊ด€ํ•œ๋‹ค.

์—ฌ๊ธฐ์„œ๋Š” ํŒŒ์ผ๋ช… Naming rule์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•˜๊ธฐ๋กœ ํ•˜์ž. ( ๊ฐ์ž ์ •ํ•ด๋„ ๋ฌด๋ฐฉ ํ•œ๋‹ค.)

   โ–บ ID_๋‚ ์งœ.ํ™•์žฅ์ž

 

        ๐Ÿ“Œ DB์— ์ด๋ฏธ์ง€ ํŒŒ์ผ ์ €์žฅ ์‹œ image_url + file_name์ž„์„ ์ƒ๊ธฐํ•˜๊ธฐ ๋ฐ”๋ž€๋‹ค.

 

๋˜ํ•œ, ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ๋งํฌ์ •๋ณด๊ฐ€ ์ˆ˜์ •/์‚ญ์ œ ์‹œ ๊ธฐ์กด ์ด๋ฏธ์ง€๋ฅผ ๋ณ€๊ฒฝํ•ด์ฃผ๋Š” ์ž‘์—…๋„ ํ•„์š”ํ•จ์œผ๋กœ ๋ฏธ๋ฆฌ ์ž‘์„ฑํ•œ๋‹ค.

"""
CHANGE  IMAGE /api/link/image/<int:id>
"""
class ImageChange(Resource):
    def put(self, id):
        f = request.files['image']
        folder = current_app.config.get('IMAGE_FORDER')
        img_url = current_app.config.get('IMAGE_URL')

        img = Image.open(f)
        # ์ด๋ฏธ์ง€๋ฅผ ๊ฐ•์ œ์ ์œผ๋กœ Resizingํ•œ๋‹ค.
        # Resizing์„ ์•ˆํ•ด๋„ ์ข‹์œผ๋‚˜, ํŒŒ์ผ ์šฉ๋Ÿ‰์„ ์ค„์ด๊ณ ์ž ๋ฐ˜์˜
        img_resize = img.resize((50,50), Image.LANCZOS)
        img_resize = ImageOps.exif_transpose(img_resize)
        img_resize = img_resize.convert('RGB')

        file_name = str(id) + '_' + str(date_function.today()) + '.' + f.filename.rsplit('.', 1)[1].lower()
        img_resize.save(folder + file_name)

        LinkModel.update_filename(id, img_url + file_name)

        return '์ด๋ฏธ์ง€๊ฐ€ ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค..'

ImageUpload() ์™€ ImageChange() ์˜ ์ฐจ์ด์ ์€ id ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š๋ƒ ์•„๋‹ˆ๋ฉด currval_id๊ฐ€์ง€๊ณ  ์ƒ์„ฑํ•˜๋Š๋ƒ ์ฐจ์ด๋‹ค.

์†Œ์Šค๋ฅผ ํ•˜๋‚˜๋กœ ๋ณ‘ํ•ฉํ•ด๋„ ๋ ์ง€ ์—ฌ๋Ÿฌ๋ถ„๋“ค์ด ์ง์ ‘ ํ•ด๋ณด์‹œ๋ฉด ์ข‹์„ ๋“ฏ ํ•˜๋‹ค.

 

โŒ˜ server.py ( ์œ„์น˜: linkservice/server/server.py )

--------------------------------------------------------------------------------------------------

API์— ImageUpload Resource์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

server.py์— ImageUpload์„ importํ•˜๊ณ ,

from src.image import ImageUpload

api resource์— ์ถ”๊ฐ€ํ•œ๋‹ค.

api.add_resource(ImageUpload, '/api/link/image')
api.add_resource(ImageChange, '/api/link/image/<int:id>')

 

โŽŸโŽœํด๋ผ์ด์–ธํŠธ๋‹จ(ํ”„๋ก ํŠธ ์—”๋“œ) API ํ˜ธ์ถœ

 

โŒ˜ api.config.js ( ์œ„์น˜: linkservice/client/src/component/api/api.config.js )

-------------------------------------------------------------------------------------------------

์„œ๋น„์Šค๋ช… API์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

api.config.js

โŒ˜ service.js ( ์œ„์น˜: linkservice/client/src/component/service/service.js )

-------------------------------------------------------------------------------------------------

linkService์—์„œ API์„ ํ˜ธ์ถœํ•˜์—ฌ ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

service.js

๋ชจ๋‘ ์ž‘์„ฑ๋˜๋ฉด, ์ด์   ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด์ž

http://localhost:3000/create ๋กœ ์ ‘์† ํ›„ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด๋ณด๊ณ  ๋“ฑ๋ก ํ•œ๋‹ค.

 

์ •์ƒ์ ์œผ๋กœ ๋“ฑ๋ก๋˜์—ˆ๋‹ค๋ฉด ์ดˆ๊ธฐํ™”๋ฉด์—์„œ ํ™•์ธํ•ด๋ณด์ž.

http://locahost:3000

 

 

 

๊ด€๋ฆฌ์ž๋ชจ๋“œ ํ™”๋ฉด์„ ๋งŒ๋“ค์–ด ๋“ฑ๋ก/ํŽธ์ง‘/์‚ญ์ œ/์ˆœ์„œ๋ณ€๊ฒฝ์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ Prototype UI์„ ๊ตฌ์„ฑํ•ด ๋ณด์ž.

๋งํฌ์„œ๋น„์Šค ๋“ฑ๋กํ™”๋ฉด์—์„œ Input Value์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•˜๋‹ค.

๊ทธ๋Ÿผ ์ด๋ฏธ์ง€ ๋“ฑ๋ก์„ ์œ„ํ•œ Drag&Drop ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ๋ฅผ ์ œ์ž‘ํ•ด๋ณด์ž.

 

#11. Reactํ™”๋ฉด์—์„œ ๊ฐ ํ•ญ๋ชฉ์˜ Input Value์„ ๊ฐ€์ ธ์˜ค๊ธฐ

 

#11. Reactํ™”๋ฉด์—์„œ ๊ฐ ํ•ญ๋ชฉ์˜ Input Value์„ ๊ฐ€์ ธ์˜ค๊ธฐ

#10. React ๋งํฌ์„œ๋น„์Šค ๋“ฑ๋ก ๋ฐ ํŽธ์ง‘ ํ™”๋ฉด ๊ฐœ๋ฐœ(๋“ฑ๋กํ™”๋ฉด ๋””์ž์ธ) #10. React ๋งํฌ์„œ๋น„์Šค ๋“ฑ๋ก ๋ฐ ํŽธ์ง‘ ํ™”๋ฉด ๊ฐœ๋ฐœ(๋“ฑ๋กํ™”๋ฉด ๋””์ž์ธ) #9. ์ดˆํ™”๋ฉด ๋””์ž์ธ ( CSS ์ ์šฉ ๋ฐ ํ™”๋ฉด Component์ œ์ž‘ ) #9. ์ดˆํ™”๋ฉด ๋””์ž์ธ

firstvalue.tistory.com

 

โŽŸโŽœ์ด๋ฏธ์ง€Drag Drop๊ธฐ๋Šฅ ๋ฐ Preview ๊ธฐ๋Šฅ

 

โŒ˜ links.create.jsx ( ์œ„์น˜: linkservice/client/src/component/links.create.jsx)

---------------------------------------------------------------------------------------------------

1)   ์ด๋ฏธ์ง€๋ฅผ ๋ฏธ๋ฆฌ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ

     preview const ์„ ์–ธ

const [preview, setPreview] = useState('assets/default.jpg')

๐Ÿ“Œ ์ด๋ฏธ์ง€๊ฐ€ ์—†์„ ๋•Œ default์ด๋ฏธ์ง€๋ฅผ ๋ณด์—ฌ ์ฃผ๊ธฐ ์œ„ํ•ด์„œ public/assets/default.jpg์„ ๋„ฃ์–ด๋‘”๋‹ค.

( ์ƒ˜ํ”Œ ์ด๋ฏธ์ง€๋Š” ์ธํ„ฐ๋„ท ๊ฒ€์ƒ‰์œผ๋กœ ์ ๋‹นํ•œ ๊ณณ์—์„œ ๊ฐ€์ ธ์˜ค๋ฉด ๋œ๋‹ค.)

 

   img src ์— preview const์„ ์ง€์ •

<div className="img_wrap" > 
            <img src =  {preview}
                alt='์ด๋ฏธ์ง€' className="img_box" />
            <input multiple={false} name='imageurl'/>
 </div>

   handleChange() ํ•จ์ˆ˜์— target name์ด imageurl์ผ ๊ฒฝ์šฐ setPreview๊ฐ’์„ ์…‹ํŒ… ํ•œ๋‹ค.

const handleChange = (e) => {
        const { name, value } = e.target
        setValues((prevValues) => ({
            ...prevValues,
            [name]: value,
        }))
        
       // ์ด ๋ถ€๋ถ„์„ ์ถ”๊ฐ€
        if (e.target.name === 'imageurl') {
            setPreview(e.target.value)
        }
    }

2)   ์ด๋ฏธ์ง€ Drag&Drop๊ธฐ๋Šฅ ๊ตฌํ˜„

Drag Drop๊ธฐ๋Šฅ์€ React์—์„œ ์ œ๊ณต๋˜๋Š” react-dropzone ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

๐Ÿ“Œ ๊ณต์‹๋ฌธ์„œ : https://github.com/react-dropzone/react-dropzone

 

ํ„ฐ๋ฏธ๋„์—์„œ yarn add react-dropzone ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•œ ๋‹ค์Œ,

useDropzone์„ ์„ ์–ธํ•œ๋‹ค.

import {useDropzone} from 'react-dropzone'

image state์„ ์„ ์–ธํ•œ๋‹ค.

const [image, setImage] = useState(null)

onDrop ๊ด€๋ จ ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

onDropํ•จ์ˆ˜๋Š” ์ด๋ฏธ์ง€ dropํ›„ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ์ž‘์—…๋‚ด์šฉ์„ ๋ฐ˜์˜ํ•˜๋ฉด ๋œ๋‹ค.

์—ฌ๊ธฐ์„œ๋Š” ํŒŒ์ผ์„ ์ฝ์–ด ์ฒ˜๋ฆฌ๋Š” ๋กœ์ง์„ ๋ฐ˜์˜ ํ•œ๋‹ค.

function onDrop(acceptedFiles) {
        const reader = new FileReader();
        const file = acceptedFiles
        
		// ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ์ฝ์–ด setImage ๋กœ ์ €์žฅ
        if (file) {
          reader.readAsDataURL(file[0]);
          setImage(file[0]);
        }
       // onDrop๋˜๋ฉด preview ๋˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ณ , ๊ธฐ์กด ์ด๋ฏธ์ง€ url์ •๋ณด๋ฅผ ๊ณต๋ฐฑ์ฒ˜๋ฆฌํ•œ๋‹ค.
        reader.onload = (e) => {
            setPreview(reader.result);
            document.getElementsByName("imageurl")[0].value = ''
        };
    }

readAsDataURL ๋ฉ”์†Œ๋“œ๋กœ imageํŒŒ์ผ์„ base64๋กœ ์ธ์ฝ”๋”ฉ ๋œ ๊ฐ’์œผ๋กœ  ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ๊ฒฐ๊ณผ๋ฅผ setPreview(๋ฏธ๋ฆฌ๋ณด๊ธฐ) ์— ๊ฐ’์„ ๋„ฃ๊ณ , setImage์€ ์ด๋ฏธ์ง€ ํŒŒ์ผ์ •๋ณด๋ฅผ ๋ณด๊ด€ํ•œ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ์ด๋ฏธ์ง€๋ฅผ ์ง์ ‘ ๋“ฑ๋กํ–ˆ์œผ๋ฏ€๋กœ,

๋งํฌ์ •๋ณด URL(imageurl) ํ•ญ๋ชฉ์„ ๋น„์šด๋‹ค.

 

Dropzone์˜ Props์ธ getRootProps์™€ getInputProps์ง€์ •ํ•œ๋‹ค.

const {getRootProps, getInputProps} = useDropzone({onDrop})

์ด๋ฏธ์ง€ ์˜์—ญ(zone)์˜ div์— props๋“ค์„ ์„ ์–ธํ•œ๋‹ค.

<div className="img_wrap" {...getRootProps()} > 
            <img src =  {preview}
                alt='์ด๋ฏธ์ง€' className="img_box" />
            <input {...getInputProps()} multiple={false} name='imageurl'/>
</div>

๐Ÿ“Œ(์ค‘์š”) getRootProps๋Š” ์ด๋ฏธ์ง€ ์˜์—ญ์˜ div์— ,    getInputProps๋Š” input์— ์„ ์–ธํ•˜๋ฉด ๋œ๋‹ค.

์ด๋ฏธ์ง€ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด์„œ handleSubmit์— ๊ด€๋ จ ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

์ด๋ฏธ์ง€๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ formDataํ˜•ํƒœ๋กœ ์—…๋กœ๋“œ๋ฅผ ์ง„ํ–‰ํ•œ๋‹ค.

linkService.createLink(values).then(
            (res) => {
                if (image) {
                    //์ด๋ฏธ์ง€ ํ•ญ๋ชฉ์ด ์žˆ์„๊ฒฝ์šฐ formData์— ๋„ฃ์–ด API์„ ํ˜ธ์ถœํ•œ๋‹ค.
                    formData.append('image', image)
                    linkService.uploadImage(formData).then(
                        (res)=> {// console.log(res) 
                        }
                )} 
                alert(res.data.message)
                window.location.reload();            
            }
        )
์ด๋ฏธ์ง€ Drag&Drop

 

๋‹ค์ŒํŽธ์—๋Š” ์‹ค์ œ DB ์™€ ์ด๋ฏธ์ง€ ํŒŒ์ผ ์ €์žฅ (๋“ฑ๋ก) ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค์–ด ๋ณด์ž.

#10. React ๋งํฌ์„œ๋น„์Šค ๋“ฑ๋ก ๋ฐ ํŽธ์ง‘ ํ™”๋ฉด ๊ฐœ๋ฐœ(๋“ฑ๋กํ™”๋ฉด ๋””์ž์ธ)

 

#10. React ๋งํฌ์„œ๋น„์Šค ๋“ฑ๋ก ๋ฐ ํŽธ์ง‘ ํ™”๋ฉด ๊ฐœ๋ฐœ(๋“ฑ๋กํ™”๋ฉด ๋””์ž์ธ)

#9. ์ดˆํ™”๋ฉด ๋””์ž์ธ ( CSS ์ ์šฉ ๋ฐ ํ™”๋ฉด Component์ œ์ž‘ ) #9. ์ดˆํ™”๋ฉด ๋””์ž์ธ ( CSS ์ ์šฉ ๋ฐ ํ™”๋ฉด Component์ œ์ž‘ ) #8. ํ”„๋ก ํŠธ์—”๋“œ ํ™”๋ฉด๊ฐœ๋ฐœ (component ์ œ์ž‘) #8. ํ”„๋ก ํŠธ์—”๋“œ ํ™”๋ฉด๊ฐœ๋ฐœ (component ์ œ์ž‘) #7. ํ”„๋ก ํŠธ์—”๋“œ

firstvalue.tistory.com

 

๋“ฑ๋ก ๋ฐ ํŽธ์ง‘ํ™”๋ฉด ๋””์ž์ธ์ด ๋˜์—ˆ์œผ๋‹ˆ, ์ด์   ์ž…๋ ฅ ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค์–ด ๋ณด์ž.

 

Reactํ™”๋ฉด์—์„œ ๊ฐ ํ•ญ๋ชฉ์˜ Input Value์„ ๊ฐ€์ ธ์˜ค๊ธฐ

โŒ˜ links.create.jsx ( ์œ„์น˜: linkservice/client/src/component/links.create.jsx)

---------------------------------------------------------------------------------------------------

 

Input value๊ฐ’์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ์‹์€ ์•„๋ž˜ ์ˆœ์„œ๋Œ€๋กœ ์ฒ˜๋ฆฌ ํ•˜์‹œ๋ฉด ๋œ๋‹ค.

1)   input์—์„œ value์„ ๋‹ด๊ธฐ ์œ„ํ•ด์„œ state์„ ์ƒ์„ฑ( jsonํ˜•ํƒœ๋กœ ๋ฐ›๊ธฐ์œ„ํ•ด์„œ ์„ ์–ธ)

const [values, setValues] = useState({})

 

2)   input์—์„œ ๊ฐ’์„ ์ž…๋ ฅํ•  ๋•Œ value state๊ฐ’์„ ๋ณ€๊ฒฝํ•ด์ฃผ๋Š” onChange handleํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

const handleChange = (e) => {
        const { name, value } = e.target
        setValues((prevValues) => ({
            ...prevValues,
            [name]: value,
        }))
}

์œ„ ์ฝ”๋“œ๋Š” input type=”text”์— ์ž…๋ ฅํ•˜๋Š” ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒ์‹œ ํ•ด๋‹น input์˜ name๊ณผ value์„ ๊ฐ€์ ธ์™€ ๊ธฐ์กด state์— ์ƒˆ๋กœ์šด state์„ ์ถ”๊ฐ€ํ•˜๋Š” ํ•ธ๋“ค ํ•จ์ˆ˜์ด๋‹ค.

preValues๊ฐ’์— setValues๊ฐ’์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

3) submit ํ•ธ๋“ค ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด values์˜ validation์ฒดํฌ์™€  API ํ˜ธ์ถœํ•˜์—ฌ ์„œ๋น„์Šค์— ์ „๋‹ฌํ•˜๊ณ ,

      useRef์„ ํ†ตํ•ด Input์— focus ์ฃผ๊ธฐ

 

useRef์„ ์–ธ

import React, { useState, useRef } from "react"

const ์„ ์–ธ

const inputFocus = useRef(null)

 handleSubmit์— validation ์ฒดํฌ ์‹œ current focus()์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

if (!values.name) {

     alert('๋งํฌ๋ช…์€ ํ•„์ˆ˜๊ฐ’์ž…๋‹ˆ๋‹ค.')
     inputFocus.current.focus()

     return false

}

 

4)   handleSubmit ํ•จ์ˆ˜

const handleSubmit = async (e) => {

        e.preventDefault();

        const formData = new FormData();
        if (!values.name) {
            alert('๋งํฌ๋ช…์€ ํ•„์ˆ˜๊ฐ’์ž…๋‹ˆ๋‹ค.')
            inputFocus.current.focus()
            return false
        }
        if (!image && !values.imageurl) {
            if (!window.confirm('์ด๋ฏธ์ง€๊ฐ€ ์—†๋Š” ๋งํฌ์ •๋ณด๋ฅผ ๋“ฑ๋กํ•˜๊ฒ ์Šต๋‹ˆ๊นŒ?')) {
                return false
            }
         }
         
         // input ํ•ญ๋ชฉ์˜ values์„ db์— ์ €์žฅํ•˜๋Š” API๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.
         
          linkService.createLink(values).then(
            (res) => {
                alert(res.data.message)
                window.location.reload();           
               }
           )
    }

 

5)   ๋ Œ๋”๋ง ๋˜๋Š” input์— onChange() ํ•จ์ˆ˜๋กœ handleChange()ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

<input type="text" name="name"
                defaultValue=""
                className="input-css"
                onChange={handleChange}
            />

 

6)   form ํƒœ๊ทธ์— onSubmit ํ•จ์ˆ˜๋กœ handleSubmitํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

<form  onSubmit={handleSubmit}>

 

Input Value ์ž‘์—…์€ ์ด๊ฒƒ์œผ๋กœ ์™„๋ฃŒํ•  ์ˆ˜ ์žˆ์œผ๋‚˜์ถ”๊ฐ€์ ์œผ๋กœ ์ด๋ฏธ์ง€๋ฅผ ๋ฏธ๋ฆฌ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ,

์ด๋ฏธ์ง€๋ฅผ ์ง์ ‘ Drag&Drop ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ์ž‘ํ•ด๋ณด์ž.

 

 

#9. ์ดˆํ™”๋ฉด ๋””์ž์ธ ( CSS ์ ์šฉ ๋ฐ ํ™”๋ฉด Component์ œ์ž‘ )

 

#9. ์ดˆํ™”๋ฉด ๋””์ž์ธ ( CSS ์ ์šฉ ๋ฐ ํ™”๋ฉด Component์ œ์ž‘ )

#8. ํ”„๋ก ํŠธ์—”๋“œ ํ™”๋ฉด๊ฐœ๋ฐœ (component ์ œ์ž‘) #8. ํ”„๋ก ํŠธ์—”๋“œ ํ™”๋ฉด๊ฐœ๋ฐœ (component ์ œ์ž‘) #7. ํ”„๋ก ํŠธ์—”๋“œ ํ™”๋ฉด ๊ฐœ๋ฐœ #7. ํ”„๋ก ํŠธ์—”๋“œ ํ™”๋ฉด ๊ฐœ๋ฐœ 1. React๋กœ ํ™”๋ฉด ๊ฐœ๋ฐœ ์ž, ์ด์ œ๋ถ€ํ„ฐ REACT๋กœ ํ™”๋ฉด์„ ๊ฐœ๋ฐœ ํ•ด๋ณด์ž. ์•ž

firstvalue.tistory.com

์ง€๊ธˆ๊นŒ์ง€ Pytho Flask Restful API ์™€ React ๋กœ ๋งํฌ์„œ๋น„์Šค์˜ ์ดˆํ™”๋ฉด์„ ์ œ์ž‘ํ–ˆ๋‹ค.

์•ž์œผ๋กœ ํ• ์ผ์„ ์ •๋ฆฌํ•ด์„œ ํ•˜๋‚˜์”ฉ ์ง„ํ–‰ํ•ด ๋ณด๊ธฐ๋กœ ํ•œ๋‹ค.

 

โŽŸโŽœTO-DO

-       ๋“ฑ๋กํ™”๋ฉด/ํŽธ์ง‘ํ™”๋ฉด ์ œ์ž‘ ( ์ด๋ฏธ์ง€ ๋“ฑ๋ก ํฌํ•จ )

-       ์ƒ๋‹จ์— ์ปค๋ฒ„ ์ด๋ฏธ์ง€ ์ถ”๊ฐ€ ( ์ปค๋ฒ„ ์ด๋ฏธ์ง€ ๋ณ€๊ฒฝ ํฌํ•จ )

-       ์ปค๋ฒ„ ์ด๋ฏธ์ง€ ๊ตฌ๊ฐ„์— ์ผ์ •์„ ํ‘œ์‹œ ( ์ผ์ •๊ด€๋ฆฌ CRUDํฌํ•จ )

-       Drag&Drop์œผ๋กœ ๋งํฌ ๋ฆฌ์ŠคํŠธ ์ˆœ์„œ ๋ณ€๊ฒฝ ( ๋ชจ๋ฐ”์ผ์—์„œ๋Š” Touch )

-       ํƒœ๊ทธ ํด๋ผ์šฐ๋“œ ํ‘œ์‹œ ( CRUD๋Š” ์ œ์™ธ)

-       ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ ( ํšŒ์›๊ฐ€์ž…๋ถ€๋ถ„์€ ์ œ์™ธ )

-       ๊ตฌ๊ธ€์• ๋„๋ฆฌํ‹ฑ์Šค(Google Analytics)์™€ ์—ฐ๊ฒฐ

 

1.1. ๋“ฑ๋กํ™”๋ฉด ์ œ์ž‘

 

โŒ˜ links.create.jsx ( ์œ„์น˜: linkservice/client/src/component/links.create.jsx)

---------------------------------------------------------------------------------------------------

๊ธฐ๋ณธ์ ์ธ ๋“ฑ๋ก ํ•ญ๋ชฉ์„ ๋งŒ๋“ ๋‹ค. ( LinkCreateMain )

import React from "react";

export default function LinkCreateMain() {
  return (
<>
    <div>
        <h1>๋งํฌ ๋“ฑ๋ก</h1>
    </div>
    <div><label>๋งํฌ๋ช…</label></div>
    <div><input type="text" name="name" /></div>
    <div><label>ํƒœ๊ทธ์ •๋ณด ( ํƒœ๊ทธ ์—ฌ๋Ÿฌ๊ฐœ ๋“ฑ๋ก์‹œ / ๋กœ ๊ตฌ๋ถ„ )</label></div>
    <div><input type="text" name="tag" /></div>
    <div><label>๋งํฌ URL (์—ฐ๊ฒฐํ•˜๊ณ ์ž ํ•˜๋Š” URL)</label></div>
    <div><input type="text" name="linkurl" /></div>
    <div><label>์‚ฌ์ง„ ๋งํฌ ์ •๋ณด</label></div>
    <div><input type="text" name="imageurl" /></div>

    <div><button type="submit">๋“ฑ๋ก</button></div>
</>
  )
}

โŒ˜ App.js์—์„œ ์ถ”๊ฐ€์ ์œผ๋กœ Route์„ ์ง€์ •ํ•œ๋‹ค.

 

App.js ์— LinkCreateMain ์˜ Route์„ ์ง€์ •ํ•œ๋‹ค.

App.js์— Route์ถ”๊ฐ€ ํ›„ http://localhost:3000/create ๋กœ ์ ‘์†ํ•ด๋ณด์ž.

 

http://localhost:3000/create

๊ทธ๋ž˜๋„ input box์— ๋Œ€ํ•œ ๋””์ž์ธ๊ณผ ์‚ฌ์ง„์„ ์ง์ ‘(PC) ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋„๋ก ๋“ฑ๋กํ™”๋ฉด์„ ์ข€ ๊ฐœ์„ ํ•ด ๋ณด๊ฒ ๋‹ค.

 

CSS์— ์•„๋ž˜ ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•œ๋‹ค. ( links.css ) 

.title-container {
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.text-title {
    text-align: center;
    font-size: 18px;
}
.input-wrap {
    display: flex;
    flex-direction: column;
    margin-top: 10px;
    margin-bottom: 20px;
}
.input-wrap > label {
    margin-bottom: 5px;
    font-size: 14px;
}
.input-box {
    height: 40px;
    width: 100%;
    position: relative;
}
.input-css {
    height: 44px;
    font-size: 14px;
    padding: 14px 12px;
    width: 100%;
    margin: 3px;
    box-sizing: border-box;
    color: #343434;
    border: 1px solid #d8d8d8;
    border-radius: 8px;
    background-color: bisque;
}

์•„๋ž˜์™€ ๊ฐ™์€ ๋งํฌ๋ช…๊ณผ ๊ฐ™์ด input-wrap ์•ˆ์œผ๋กœ ๋‹ค๋ฅธ ํ•ญ๋ชฉ๋“ค๋„ ๋ชจ๋‘ ์ž‘์„ฑํ•œ๋‹ค.

input-wrap > input-box > input-css ์„ ๊ฐ ํ•ญ๋ชฉ์— ์ˆœ์ฐจ์ ์œผ๋กœ ์ ์šฉ

input-wrap > input-box > input-css ์„ ๊ฐ ํ•ญ๋ชฉ์— ์ˆœ์ฐจ์ ์œผ๋กœ ์ ์šฉ

์ผ๋ฐ˜ INPUT BOX๋Š” ์œ„์™€ ๊ฐ™์ด ์ ์šฉํ•˜๋ฉด ๋˜๋‚˜, ์ด๋ฏธ์ง€ ๋ถ€๋ถ„์€ PC์— ์žˆ๋Š” ์ด๋ฏธ์ง€๋ฅผ ์ง์ ‘ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๊ฒŒ ๊ธฐ๋Šฅ์„ ๋ถ€์—ฌํ•˜๊ธฐ ์œ„ํ•ด์„œ ์•„๋ž˜ ๋ถ€๋ถ„์„ ์ถ”๊ฐ€์ ์œผ๋กœ ๋ฐ˜์˜ํ•œ๋‹ค.

.img_contain {
    display: flex;
margin-top: 10px;
    margin-bottom: 20px;
}
.img_wrap {
    display: flex;
    width: 50%;
 }
.img_box {
    align-items: center;
    width: 100%;
    height: 150px;
    object-fit: cover;
    border-radius: 4px;
    background-position: 50%;
    background-repeat: no-repeat;
    background-size: cover;
    background-color: gray;
    cursor: pointer;
}
.text_type {
    display: flex;
    align-items: flex-end;
    margin-left: 10px;
  }

links.create.jsx์— ์ด๋ฏธ์ง€๋ฅผ ์ง์ ‘๋“ฑ๋กํ•˜๋Š” ๋ถ€๋ถ„์„ ์ถ”๊ฐ€

http://localhost:3000/create ๋กœ ํ™•์ธํ•ด๋ณด์ž.

 

๋งํฌ์„œ๋น„์Šค ๋“ฑ๋กํ™”๋ฉด

๋””์ž์ธ์ด ์–ด๋ ต๋‹ค๋ฉด bootstrap์— ์ œ๊ณต๋˜๋Š” form styles์„ ํ™œ์šฉํ•ด๋„ ๋œ๋‹ค

์ฐธ๊ณ  : https://getbootstrap.com/docs/4.0/components/forms/

 

โŽŸโŽœ๋””์ž์ธ ๋ถ€๋ถ„์€ ์ด์ •๋„๋กœ ๋๋‚ด๊ณ  ๋‹ค์Œ์—๋Š” ๊ธฐ๋Šฅ ๊ตฌํ˜„์„ ํ•ด๋ณด์ž.

 


 

#8. ํ”„๋ก ํŠธ์—”๋“œ ํ™”๋ฉด๊ฐœ๋ฐœ (component ์ œ์ž‘)

 

#8. ํ”„๋ก ํŠธ์—”๋“œ ํ™”๋ฉด๊ฐœ๋ฐœ (component ์ œ์ž‘)

#7. ํ”„๋ก ํŠธ์—”๋“œ ํ™”๋ฉด ๊ฐœ๋ฐœ #7. ํ”„๋ก ํŠธ์—”๋“œ ํ™”๋ฉด ๊ฐœ๋ฐœ 1. React๋กœ ํ™”๋ฉด ๊ฐœ๋ฐœ ์ž, ์ด์ œ๋ถ€ํ„ฐ REACT๋กœ ํ™”๋ฉด์„ ๊ฐœ๋ฐœ ํ•ด๋ณด์ž. ์•ž์—์„œ ์‚ฌ์ „ ์ค€๋น„์‹œ ํ”„๋กœ์ ํŠธ ํด๋”์ธ linkservice์•„๋ž˜์— npx create-react-app ์œผ๋กœ ์ด๋ฏธ c

firstvalue.tistory.com

 

๋ฐ์ดํ„ฐ๊นŒ์ง€ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ–ˆ์œผ๋‹ˆ, ์ด์   ๋””์ž์ธ์„ ํ•ด๋ณด์ž.

 

โŒ˜ links.css ( ์œ„์น˜: linkservice/client/src/css/links.css)

โŒ˜ links.jsx ( ์œ„์น˜: linkservice/client/src/component/links.jsx)

---------------------------------------------------------------------------------------------------------

CSS์„ ์œ„ํ•œ ํด๋”๋ฅผ ๋งŒ๋“ค๊ณ  links.cssํŒŒ์ผ์„ ์ƒ์„ฑํ•œ๋‹ค.

๋ชจ๋“  CSS๋Š” links.css์— ๋„ฃ์–ด๋‘์—ˆ๋‹ค.

 

link.css์— ์šฐ์„  link-container์™€ link-wrapper์˜์—ญ์„ ๋งŒ๋“ค์–ด์ฃผ๊ณ ,

.link-container {
    margin-bottom: 6px;
    cursor: move;
}
.link-wrapper {
    padding: 12px;
box-shadow: 0em 0em 0.1em #000000;
    border-radius: 10px;
}

link-show ์˜์—ญ์—๋Š” link image์™€ link name์ด ๋ณด์—ฌ์ฃผ๋„๋ก ํ•œ๋‹ค.

.link-show {
    display: flex;
    align-items: center;
}
.link-img {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    object-fit: cover;
}
.link-title {
    flex-direction: column;
    margin-left: 10px;
min-width: 200px;
    width: 70%;
    font-weight: 600;
}

 

โŒ˜ links.jsx์—์„œ CSS์„ importํ•˜๊ณ  CSS๋“ค์„ ์ ์šฉํ•ด๋ณธ๋‹ค.

links.jsx์— links.css importํ•œ๋‹ค.
div className์„ ์ ์šฉํ•œ๋‹ค.

โŽฎ๊ฒฐ๊ณผํ™”๋ฉด

 

๋งํฌ์ •๋ณด๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ ๋“ฑ๋กํ•ด์„œ ํ™•์ธํ•ด ๋ณธ๋‹ค.

JSON ๋ฐ์ดํ„ฐ ์ค‘ ํ™œ์šฉํ•˜์ง€ ์•Š๋Š” ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€์ ์œผ๋กœ ํ™”๋ฉด์— ์ ์šฉํ•ด๋ณด์ž.

 

โŽฎ๋งํฌ์ •๋ณด ๊ฑธ๊ธฐ

 

link-show ๋ถ€๋ถ„์— onClick์ด๋ฒคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

์ด์   ํด๋ฆญํ•˜๋ฉด linkurlํ•ญ๋ชฉ์˜ ์ •๋ณด์— ๋”ฐ๋ผ ํŽ˜์ด์ง€ ์ด๋™์ด ๋œ๋‹ค.

 

 

โŽฎํƒœ๊ทธ์ •๋ณด ํ‘œ์‹œ

 

link-wrapper ๋ถ€๋ถ„์— tag ํ‘œ์‹œ๋ฅผ ํ•ด๋ณด์ž.

ํƒœ๊ทธ๋Š” ์—ฌ๋Ÿฌ ๊ฐœ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋‚˜, ์—ฌ๊ธฐ์„œ๋Š” ์ตœ๋Œ€ 3๊ฐœ๊นŒ์ง€๋งŒ ์ž…๋ ฅ์ด ๊ฐ€๋Šฅ(ํ™”๋ฉด ์ œ์•ฝ์‚ฌํ•ญ) ํ•˜๊ณ ,

ํƒœ๊ทธ์˜ ๊ตฌ๋ถ„์ž๋Š” / ๋กœ ํ•ด์„œ ๊ตฌ๋ถ„ํ•œ๋‹ค. (์˜ˆ,  ํƒœ๊ทธ1/ํƒœ๊ทธ2/ํƒœ๊ทธ3 )

 

ํƒœ๊ทธ์˜ ๋‚ด์šฉ์„ ํ‘œ์‹œํ•ด์ค€๋‹ค.

ํƒœ๊ทธ ํ‘œ์‹œ๋ฅผ ์œ„ํ•œ html ์ถ”๊ฐ€

ํƒœ๊ทธ ๊ด€๋ จ css์„ ์ถ”๊ฐ€ํ•œ๋‹ค

.tag-container {
    display: flex;
    flex-wrap: nowrap;
}
.tag-row {
    flex-basis: 90px;
}
.tag-button {   
    flex-basis: 21%;
    height: 20px;
    border-radius: 6px;
    margin-right: 15px;
    text-align: center;
    font-size: 4px;
    color: rgb(218, 213, 213);
}
.tag-blue {
    background-color: rgb(82, 65, 234); 
}

ํƒœ๊ทธ ํ‘œ์‹œ

๋งˆ์ง€๋ง‰ ํƒœ๊ทธ๋Š” ์ œ์ฃผ๋„/๊ทค/ํ• ์ธ ์—ฌ๋Ÿฌ ๊ฐœ๋กœ ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ถ„ํ• ์„ ํ•ด์•ผ ํ•œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์—ฌ๋Ÿฌ ๊ฐœ ํƒœ๊ทธ๋„ ๊ฐ๊ฐ ์ƒ‰์ƒ์„ ๋‹ค๋ฅด๊ฒŒ ๋ฒ„ํŠผ์„ ๋งŒ๋“ค์–ด ๋ณด์ž.

 

tag button ์ƒ‰์ƒ์„ ๋‹ค๋ฅด๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ classList์— ๋‹ค๋ฅธ ์ƒ‰์ƒ์„ ๋‹ด์•„ ์ ์šฉํ•ด๋ณด์ž.

 

์ฐธ๊ณ ๋กœ, ์กฐ๊ธˆ ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœchild component์ธ TagList(Props)์„ ๋งŒ๋“ค์–ด ๋ Œ๋”๋ง์„ ํ•˜๋Š” ๋ฐฉ์‹๋„ ์žˆ๋‹ค.

child component ์ธ TagList(Props)

์œ„ ์†Œ์Šค์—์„œ ๋ˆ„๋ฝ๋œ ๊ฒŒ ํ•˜๋‚˜ ์žˆ๋‹ค.

๋ฐ”๋กœ tag ๊ฐ’์ด ์—†์„ ๊ฒฝ์šฐ๋„ ๋นˆ ํ•ญ๋ชฉ์— tag-orange css๊ฐ€ ์ ์šฉ๋˜์–ด ๋ฒ„๋ฆฐ๋‹ค.

ํƒœ๊ทธ๊ฐ€ ์—†์„๊ฒฝ์šฐ ๋นˆ์นธ์œผ๋กœ ํ‘œ์‹œ๋œ๋‹ค.

 

๊ทธ๋ž˜์„œ tag๊ฐ’์ด ์žˆ์„ ๊ฒฝ์šฐ๋งŒ ๋ Œ๋”๋ง ๋˜๋„๋ก ์กฐ๊ฑด์„ ํ•˜๋‚˜ ๋” ์ค˜์„œ ํ•ด๊ฒฐํ•œ๋‹ค.

!tag ์„ or ์กฐ๊ฑด์œผ๋กœ ์ถ”๊ฐ€ํ•œ๋‹ค.

!tag || tag.split('/').map( (tag, index)=>

๋ฌผ๋ก  child function์ธ TagList(Props)์„ ์•ˆํ•˜๊ณ ,

LinkMain()์—์„œ ๋ฐ”๋กœ ๋ Œ๋”๋ง์„ ํ•ด๋„ ๋ฌด๋ฐฉํ•œ๋‹ค. ์•„๋ž˜ ๊ฐ™์€ ์‹์œผ๋กœ ํ•ด๋„ ๊ฒฐ๊ณผ๋Š” ๊ฐ™์œผ๋‹ˆ ์†Œ์Šค ์ฐธ๊ณ ๋งŒ ํ•˜๊ธธ ๋ฐ”๋ž€๋‹ค.

 

๊ฐœ๋ฐœ ์†Œ์Šค ๋ฐฉ์‹์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฐฉ์‹์ด๋‹ค. ๋ญ๊ฐ€ ์˜ณ๋‹ค, ๊ทธ๋ฅด๋‹ค ์„ ํŒ๋‹จํ•˜๋Š” ๊ฒƒ์€ ๋งž์ง€ ์•Š๋‹ค. ๋‹จ, ์ข€๋” ๊ฐ€๋…์„ฑ๊ณผ Performance, ์œ ์ง€๋ณด์ˆ˜ ๋“ฑ์„ ๊ณ ๋ คํ•˜์—ฌ ํšŒ์‚ฌ๊ธฐ์ค€ ํ˜น์€ ๊ฐœ๋ฐœ์ž๋“ค๊ณผ ํ˜‘์—…ํ•ด์„œ ์ฝ”๋”ฉ ๊ธฐ์ค€์— ๋งž์ถฐ์„œ ๊ฐœ๋ฐœํ•˜๋ฉด ๋œ๋‹ค.

 

์ด์  , ์ƒ‰์ƒ๊ด€๋ จ css๋ฅผ ์ถ”๊ฐ€ํ•˜์ž.

.tag-orange {
    background-color: rgb(200, 173, 108);
}
.tag-grin {
    background-color: rgb(18, 161, 139); 
}

 

์‹ค์ œ ๋ณต์žกํ•˜๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ์ง€๋งŒ, ์ „์ฒด์ ์œผ๋กœ ํฌ๋งท๋งŒ ์ดํ•ดํ•œ๋‹ค๋ฉด ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š”๋ฐ ๋งŽ์€ ๋„์›€์ด ๋  ๊ฒƒ์œผ๋กœ ์ƒ๊ฐ๋œ๋‹ค.

 

ํ•˜์ง€๋งŒ, ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋ฉด์„œ ์–ด๋ ค์› ๋˜ ๊ธฐ์–ต์ด ์žˆ์–ด์„œ ์ด์ œ๋ถ€ํ„ฐ

TO-DO ๋ฆฌ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค์–ด ํ•˜๋‚˜์”ฉ ์™„์„ฑํ•ด๋ณด์ž.

( ํ™”๋ฉด์„ค๊ณ„๋ฅผ ๋‹ค์‹œ ํ•œ๋ฒˆ ์ฐธ๊ณ ํ•˜๊ธธ ๋ฐ”๋ž€๋‹ค.)

 


๋‹ค์ŒํŽธ์—๋Š” ๋งํฌ์„œ๋น„์Šค์˜ ๋“ฑ๋กํ™”๋ฉด์„ ์ œ์ž‘ํ•ด๋ณด์ž.

#7. ํ”„๋ก ํŠธ์—”๋“œ ํ™”๋ฉด ๊ฐœ๋ฐœ

 

#7. ํ”„๋ก ํŠธ์—”๋“œ ํ™”๋ฉด ๊ฐœ๋ฐœ

1. React๋กœ ํ™”๋ฉด ๊ฐœ๋ฐœ ์ž, ์ด์ œ๋ถ€ํ„ฐ REACT๋กœ ํ™”๋ฉด์„ ๊ฐœ๋ฐœ ํ•ด๋ณด์ž. ์•ž์—์„œ ์‚ฌ์ „ ์ค€๋น„์‹œ ํ”„๋กœ์ ํŠธ ํด๋”์ธ linkservice์•„๋ž˜์— npx create-react-app ์œผ๋กœ ์ด๋ฏธ client์„ ์ƒ์„ฑํ–ˆ๋‹ค. #4. ํ”„๋กœ์ ํŠธ ํด๋” ๊ตฌ์กฐ ๋ฐ ํ™˜๊ฒฝ์„ค

firstvalue.tistory.com

 

1.1. Component ์ œ์ž‘ ( React๋กœ API ํ˜ธ์ถœ )

์ด์ œ๋ถ€ํ„ฐ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ•˜๋‚˜์”ฉ ์ž‘์„ฑํ•ด ๋ณด์ž.

React์—์„œ Axios๋ฅผ ์ด์šฉํ•ด API ํ˜ธ์ถœํ•˜๊ธฐ

์ปดํฌ๋„ŒํŠธ๋Š” ํฌ๊ฒŒ 3๊ฐ€์ง€๋กœ ๊ตฌ๋ถ„ํ•˜์—ฌ ์ž‘์„ฑํ–ˆ๋‹ค.

1.    API URL์„ ๊ด€๋ฆฌํ•˜๋Š” api.config.js

2.    AXIOS๋กœ API์„ ํ˜ธ์ถœํ•˜์—ฌ ๋ฆฌํ„ด ๋ฐ›๋Š” service.js

3.    service์„ ๋ฐ›์•„ ํ™”๋ฉด์— ์ถœ๋ ฅํ•ด์ฃผ๋Š” ์ปดํฌ๋„ŒํŠธ๋“ค

 

๋””๋ ‰ํ† ๋ฆฌ๋Š” src ๋ฐ‘์— /component ์— /api ์™€ /service ๋กœ ๋‚˜๋ˆˆ๋‹ค.

 

โŒ˜ api.config.js ( ์œ„์น˜: linkservice/client/src/component/api/api.config.js)

----------------------------------------------------------------------------------------------

Python Server์—์„œ ๊ฐœ๋ฐœํ•œ API ํ˜ธ์ถœ URL์„ ๊ด€๋ฆฌํ•œ๋‹ค. 

API๊ฐ€ ๋งŽ์•„์ง€๋ฉด ๋ณ„๋„ ๊ด€๋ฆฌ ํ•˜๋Š”๊ฒŒ ์œ ์ง€๋ณด์ˆ˜์ƒ ์ข‹๋‹ค.

 

export const API = {
    //LINKS ์„œ๋น„์Šค API  
    LINKSALL: `/api/links`,
    LINKREGISTER: `/api/link/create`,
    LINKSELECT: `/api/link/select/`,
    LINKDELETE: `/api/link/delete/`,
    LINKUPATE: `/api/link/update`,
}

โŒ˜ service.js ( ์œ„์น˜: linkservice/client/src/component/service/service.js)

------------------------------------------------------------------------------------------------

service.js๋Š” ๊ฐ API ํ˜ธ์ถœ์„ ๋‹ด๋‹นํ•˜๋Š” ํ•จ์ˆ˜๋“ค์„ ๋ชจ์•„๋‘” ์„œ๋น„์Šค๋กœ,

axios ๋กœ API์„ ํ˜ธ์ถœํ•˜์—ฌ ๊ฐ’์„ return๋ฐ›๋„๋ก ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•˜๊ณ , ์™ธ๋ถ€์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋„๋ก linkService๋กœ ์ง€์ •ํ•œ๋‹ค.

import axios from "axios"
import { API } from "../api/api.config"

// ####### ๋งํฌ์ •๋ณด API ํ˜ธ์ถœ
const getLinks = () => {
    return axios.get(API.LINKSALL)
}
const getSelectLink = (id) => {
    return axios.get(API.LINKSELECT + id)
}
const createLink= (values) => {
    return axios.post(API.LINKREGISTER, values)
}
const updateLink = (id, values) => {
    return axios.put(API.LINKUPATE + id, values)
}
const deleteLink = (id) => {
    return axios.put(API.LINKDELETE + id)
}
const linkService = {
    getLinks,
    getSelectLink,
    createLink,
    updateLink,
    deleteLink,
}
export default linkService;

1.2.  ํ™”๋ฉด ์ถœ๋ ฅ

ํ™”๋ฉด ์†Œ์Šค๋Š” .jsx ํ™•์žฅ์ž๋กœ ์ƒ์„ฑํ–ˆ๋‹ค.

 

โŒ˜ links.jsx ( ์œ„์น˜: linkservice/client/src/component/links.jsx)

--------------------------------------------------------------------------------------------------

component์•ˆ์— API์„ ํ˜ธ์ถœํ•˜์—ฌ ๋ฆฌํ„ด ๋ฐ›์€ ๊ฐ’์„ ํ™”๋ฉด์— ์ถœ๋ ฅํ•ด๋ณด์ž.

useEffect์„ ํ†ตํ•ด linkService์˜ getLinks(์ „์ฒด๋งํฌ๋ชฉ๋ก์กฐํšŒ)์„ ํ˜ธ์ถœํ•˜๋Š” ๋กœ์ง์ด๋‹ค.

rendering๋˜๋Š” return๊ฐ’์— <></> Fragment์ถ•์•ฝ์œผ๋กœ ๊ฐ์‹ธ ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•˜๊ธฐ ๋ฐ”๋ž€๋‹ค.

 

import React, { useState, useEffect } from "react"
import linkService from "./service/service"

export default function LinkMain() {
    const [data, setData] = useState([])

    useEffect(() => {
        linkService.getLinks().then(
        (res)=> {
            setData(res.data)
           console.log(res.data)
        }, (error) => {
           alert(error.res.data.message )
        }
        )
    }, [])
    
    return (
<> 
     <div>
       { data.links && data.links.map((links) => (

        <div key={links.id}> 
            <div >id : {links.id}</div>
            <div>name : {links.name}</div>
            <div>tag : {links.tag}</div>
            <div>imageurl : {links.imageurl}</div>    
            <div>linkurl : {links.linkurl}</div>   
        </div>
                                                              
        ))}
     </div>
 </> 
    ) // return
} // function

 

links.jsx์˜ LinkMain()์„ App.js์—์„œ Router์„ ์ง€์ •ํ•˜์—ฌ ํ˜ธ์ถœํ•ด๋ณด์ž.

 

โŒ˜ App.js ( ์œ„์น˜: linkservice/client/src/App.js)

-------------------------------------------------------------------------------------------------------

import React from "react"
import { BrowserRouter, Route, Routes } from "react-router-dom"
import LinkMain from "./component/links"

function App() {
  return (
    <BrowserRouter>    
      <Routes>
        <Route exact path="/" element={<LinkMain />} />  
      </Routes> 
    </BrowserRouter>
  );
}
export default App;

 

ํ„ฐ๋ฏธ๋„์„ ์—ด์–ด์„œ yarn start์„ ์‹คํ–‰ํ•ด๋ณด์ž. ํ•˜์ง€๋งŒ, ๋ชจ๋“ˆ ์˜ค๋ฅ˜๊ฐ€ ๋‚ ๊ฒƒ์ด๋‹ค.

๊ด€๋ จ ๋ชจ๋“ˆ์„ ์„ค์น˜ ์•ˆ ํ•ด์„œ ๋‚˜๋Š” ๊ฒƒ์œผ๋กœ ์šฐ์„  ๋ชจ๋“ˆ ์„ค์น˜๋ฅผ ํ•˜์ž.

 

$ yarn add react-router-dom

$ yarn add axios

 

$yanr start

 

๋ชจ๋“ˆ ์„ค์น˜ ํ›„ ์‹คํ–‰ํ•˜๋ฉด ๋นˆ ํ™”๋ฉด์— ๋ธŒ๋ผ์šฐ์ € ๊ฐœ๋ฐœ์ž ๋ชจ๋“œ์— ๋ณด๋ฉด 404์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

ํฌ๋กฌ์—์„œ ๊ฐœ๋ฐœ์ž๋„ ๋ชจ๋“œ์—์„œ ํ™•์ธ

์˜ค๋ฅ˜ Url์„ ๋ณด๋ฉด API ํ˜ธ์ถœ ์‹œ ๋ฐœ์ƒํ•œ๋‹ค.

React์—์„œ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๋กœ API ์š”์ฒญ ์‹œ Proxy ์„ค์ •์„ ํ•ด์ค˜์•ผ ์ •์ƒ ํ˜ธ์ถœ์ด ๋œ๋‹ค.

์šฐ๋ฆฌ๋Š” Python Flask๋กœ API์„ ๊ตฌํ˜„ ํ–ˆ์œผ๋ฏ€๋กœ, Python Server๋‹จ์„ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•˜๋ฉด ๋œ๋‹ค.

 

โŽฎProxy ์„ค์ •

 

client์˜ package.json ์—ด์–ด proxy์„ค์ •์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

"proxy": "http://0.0.0.0:5000",

package.json์— proxy์„ค์ •

   ๐Ÿ“Œ ์•„์ดํ”ผ๋ฅผ 127.0.0.1๋กœ ์„ค์ •์•ˆํ•˜๊ณ , 0.0.0.0 ๋กœ ์„ค์ •ํ•œ ์ด์œ ๋Š” ํด๋ผ์šฐ๋“œ ํ™˜๊ฒฝ(AWS, CAFE24๋“ฑ) ๊ฒฝ์šฐ ๋‚ด๋ถ€ IP๊ฐ€ 192.168๋กœ ์‹œ์ž‘๋  ์ˆ˜ ์žˆ์–ด์„œ ๋ชจ๋“  IPv4 ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์„ค์ •ํ•˜๊ณ ์ž 0.0.0.0๋กœ ํ–ˆ๋‹ค.

 

์ด์ œ ์‹คํ–‰ํ•ด๋ณด์ž

โŽฎ์‹คํ–‰๋ฐฉ๋ฒ•

    -       ์„œ๋ฒ„ ( $ linkservice/server ) ํ„ฐ๋ฏธ๋„

          1.    python ๊ฐ€์ƒํ™” ์‹คํ–‰ ( source ./bin/activate )

          2.    python3 server.py ์‹คํ–‰

          3.    POSTMAN์„ ํ†ตํ•ด์„œ ์ƒ˜ํ”Œ ๋ฐ์ดํƒ€๋ฅผ ์ž…๋ ฅํ•œ๋‹ค. 

          โ–บ POST /api/link/create - ๋งํฌ์ •๋ณด ๋“ฑ๋ก ์ฐธ๊ณ 

#6. Python์œผ๋กœ API ๊ฐœ๋ฐœ(API Resource์„ ์–ธ ๋ฐ ํ…Œ์ŠคํŠธ)

 

#6. Python์œผ๋กœ API ๊ฐœ๋ฐœ(API Resource์„ ์–ธ ๋ฐ ํ…Œ์ŠคํŠธ)

#6. Python์œผ๋กœ API ๊ฐœ๋ฐœ(๋ฐ์ดํƒ€๋ฒ ์ด์Šค ๋ชจ๋ธ ์ž‘์—…) #6. Python์œผ๋กœ API ๊ฐœ๋ฐœ(๋ฐ์ดํƒ€๋ฒ ์ด์Šค ๋ชจ๋ธ ์ž‘์—…) #5. Python์œผ๋กœ API ๊ฐœ๋ฐœ(๊ธฐ๋ณธํ™˜๊ฒฝ๊ตฌ์„ฑ) #5. Python์œผ๋กœ API ๊ฐœ๋ฐœ(๊ธฐ๋ณธํ™˜๊ฒฝ๊ตฌ์„ฑ) โŒ˜ server.py ( ์œ„์น˜: linkservice/

firstvalue.tistory.com

 

-       ํด๋ผ์ด์–ธํŠธ ( $ linkservice/client ) ํ„ฐ๋ฏธ๋„

       1.    yarn start ์‹คํ–‰

 

๋ธŒ๋ผ์šฐ์ €์—์„œ http://localhost:3000 ์œผ๋กœ ํ™•์ธํ•ด๋ณด์ž.

์ •์ƒ์ ์œผ๋กœ ์ถœ๋ ฅ๋จ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

ํฌ๋กฌ ๊ฐœ๋ฐœ์ž๋ชจ๋“œ Console

๐Ÿ“Œ links.jsx ์†Œ์Šค์— API์„ ํ†ตํ•ด JSON์„ ์ œ๋Œ€๋กœ ๋ฐ›์•„์˜ค๋Š”์ง€ ํ™•์ธํ•˜๊ณ ์ž useEffect ๋ถ€๋ถ„์— console.log์„ ์ฐ์–ด ๋ธŒ๋ผ์šฐ์ €

     ๊ฐœ๋ฐœ์ž๋ชจ๋“œ์— console์— {links: Array(1)} ๊ฐ€ ๋ณด์ธ๋‹ค.

 


ํ™”๋ฉด์ด ์ด์˜์ง€ ์•Š์•„, ๋‹ค์Œ ํฌ์ŠคํŒ…์—๋Š” ๋””์ž์ธ์„ ํ•ด๋ณด์ž.

+ Recent posts