[React] 카카오 Map API - Polyline 이미지 업로드

또여·2021년 8월 26일
0

React 프로젝트

목록 보기
8/20
post-custom-banner

각각 선택한 선과 마커(이동중, 방문장소)에 사진과 텍스트를 저장시켜야 하므로 사진을 업로드 하는 부분을 해보았다

0. 결과물

1. 구현 내용

사진을 여러건 업로드 해야한다
업로드하면 바로 서버에 저장되고,
저장된 경로를 프론트에서 받아서 화면에 보여준다
사진을 클릭하면 지워진다

2. react-dropzone

https://react-dropzone.js.org/

링크에 들어가보면 react-dropzone에 대한 내용이 자세히 나와있다
주로 Hook을 이용한 예시가 많은데, 난 컴포넌트로 그렸다


59번 라인처럼 아이콘을 추가하고, 53번에 style을 줘서 크기 등을 조절해주었다
파일을 업로드 할 때 가장 중요한 props인 onDrop에 함수를 아래와 같이 처리했다. 여러건 하게될 경우 multiple속성을 주면된다

여러건 처리를 하게 되므로 formData에 넘겨받은 files를 하나씩 담아준다
이때 19번 줄에 "file"이라고 있는데, 이 파일을 서버로 넘길 때 구분자 같은 개념이라고 생각하면 되겠다. 사실 이미지를 올리니 "image"라고 해주는게 좋을 수 있다. 서버에서 upload 부분을 처리할 때, 저 옵션으로 분기처리할 수 있기 때문에..

21번 라인에서 속성도 저런식으로 주고 26번 라인에서 api로 post하면 서버로 데이터가 전달된다

28번 라인은 서버에 저장 성공 후 response를 받으면, 그때 파일이 저장된 경로를 넘겨받는다. 이걸 setImage(newImgList) 해주어서 이미지가 저장된 경로를 저장해두고 화면에 뿌려주거나, 저장 시 활용하도록 한다

3. 이미지 저장 Back-end

multer라는 라이브러리를 사용했다

193번 라인에서 post로 받고나서 197번에 upload를 태운다
191번 라인에서 upload변수에 storage는 파일 저장 위치와 저장시킬때 파일 명을 결정해주고, .array('file',10)은 받은 데이터를 array로 받으며, 그때 구분자를 file로 받으며, 최대 크기는 10으로 지정한다는 의미!
자세한건 문서와 구글링하면 금방 나온다
. array로 받게되면 뒤에 최대 파일 수는 얼마던지 기입을 해줘야 동작하는것 같다

4. 남은 일

  • polyline.js에서 저장버튼을 눌렀을 때, 이미지와 텍스트, 설명 데이터를 post로 서버에 저장시키기

5. 소스

polyline.js

import React, { useEffect, useState } from 'react'
import { Button, Form, Input } from 'antd'
import ImageUpload from '../../../utils/ImageUpload'
import _ from 'lodash';

function Polyline(props) {
    const { TextArea } = Input
    const [CurData, setCurData] = useState([])
    const [HeaderText, setHeaderText] = useState("")
    
    useEffect(() => {
        setCurData(props.polyline)
    }, [props.polyline])

    useEffect(() => {
        if(CurData) {
            const tempIdx = _.get(CurData[0], 'index') ? _.get(CurData[0], 'index') : "항목을 선택하세요"
            setHeaderText(`# ${tempIdx}`)
        }
    }, [CurData])


    const submitHandler = (event) => {
        event.preventDefault() //확인눌렀을때 화면을 다시 그리지 않게 해줌
    }
    
    
    return (
        <div>
            {CurData && 
                <Form onSubmit={submitHandler}>
                    <div>
                        <ImageUpload
                            imgFlag={HeaderText}/>                                        
                    </div>

                    <div>
                        <div style={{margin: "10px", fontSize: "16px"}}>
                            {HeaderText}
                        </div>
                        <div style={{margin: "10px"}}>
                            <label>Title</label>
                            <Input value={CurData.endTime} />
                        </div>

                        <div style={{margin: "10px"}}>
                            <label>Description</label>
                            <TextArea value={CurData.endTime} rows={4}/>
                        </div>

                        
                        <div style={{margin: "10px"}}>
                            <Button htmlType="submit">
                                저장
                            </Button>
                        </div>
                    </div>
            </Form>
            }
        </div>
    )
}

export default Polyline

ImageUpload.js

import React, { useEffect, useState } from 'react'
import Dropzone from 'react-dropzone'
import { Icon } from 'antd'
import axios from 'axios'

function FileUpload(props) {

    const [Images, setImages] = useState([])
    const [DisabledFlag, setDisabledFlag] = useState(true)

    useEffect(() => {
        setImages([])
        props.imgFlag === '# 항목을 선택하세요' ? setDisabledFlag(true) : setDisabledFlag(false)
    }, [props.imgFlag])

    const dropHandler = (files) => {
        let formData = new FormData()
        for(let i = 0; i < files.length; i++){
            formData.append("file", files[i])
        }
        const config = {
            headers: {
                'Content-Type': 'multipart/form-data'
            }
        };
        axios.post('/api/polyline/image', formData, config)
        .then(response => {
            if(response.data.success){
                let newImgList = [...Images]
                response.data.files.map((cur) => {
                    newImgList.push(cur.path)
                })
                setImages(newImgList)
            } else{
                alert('파일 저장 실패!')
            }
        })
    }
    

    const deleteHandler = (image) => {
        setImages(Images.filter((key) => key !== image))
    }

    return (
        <div style={{display:'flex', justifyContent:'space-between'}}>
            <Dropzone
                //disabled={DisabledFlag} 
                onDrop={dropHandler}
                multiple>
                {({getRootProps, getInputProps}) => (
                <section>
                <div style={{
                    width:300, height: 240, border:'1px solid lightgray',
                    display:'flex', alignItems: 'center', justifyContent:'center'
                    }}
                    {...getRootProps()}>
                    <input {...getInputProps()} />
                    <Icon type="plus" style={{ fontSize:'3rem'}}/>
                </div>
                </section>
            )}
            </Dropzone>

            <div style={{ width:'350px', height: '240px', overflowY:'scroll'}}>
                {Images.map((image, index) => (
                    <div key={index} onClick={() => deleteHandler(image)}>
                        <img style={{minWidth:'300px', width:'300px', height:'240px'}}
                        src={`http://localhost:5000/${image}`}/>
                    </div>
                ))}
            </div>
        </div>
    )
}

export default FileUpload
profile
기록 열심히하는 개발자인척
post-custom-banner

0개의 댓글