React - 파일 데이터 전송하기(파일 업로드, 파일 미리보기)

2021. 5. 27. 20:35React

반응형

에이투 유선 텐키리스 기계식 적축 게이밍 키보드, AG0301, 블랙ESR 아이패드 에어4 블루투스 피봇 마그네틱 키보드 케이스, 블랙 EC067ESR 아이패드 에어4 블루투스 피봇 마그네틱 키보드 케이스, 블랙 EC067

리액트에서 파일 업로드를 하는 방법은 기존 ajax를 사용하는 방법과 크게 다르지 않다.

 

스테이트에 파일 데이터를 넣는 방법이 다른 부분이 있어서 해당 부분부터 처리를 해야한다.

 

먼저 아래와 같이 두개의 스테이트를 생성한다.

 

 const [imgBase64, setImgBase64] = useState([]); // 파일 base64
 const [imgFile, setImgFile] = useState(null);	//파일	

imgFile은 이미지 파일 그 자체를 받을 스테이트이고, imgBase64는 미리보기를 구현하기 위해서 이미지 데이터를 받을 스테이트이다.

 

아래와 같이 input을 만들어준다.

 <input type="file" id="file"  onChange={handleChangeFile} multiple="multiple" />

위 input에 파일을 등록하면 handleChangeFile 함수가 실행한다.

 

  const handleChangeFile = (event) => {
    console.log(event.target.files)
    setImgFile(event.target.files);
    //fd.append("file", event.target.files)
    setImgBase64([]);
    for(var i=0;i<event.target.files.length;i++){
    if (event.target.files[i]) {
      let reader = new FileReader();
      reader.readAsDataURL(event.target.files[i]); // 1. 파일을 읽어 버퍼에 저장합니다.
      // 파일 상태 업데이트
      reader.onloadend = () => {
        // 2. 읽기가 완료되면 아래코드가 실행됩니다.
        const base64 = reader.result;
        console.log(base64)
        if (base64) {
        //  images.push(base64.toString())
        var base64Sub = base64.toString()
           
        setImgBase64(imgBase64 => [...imgBase64, base64Sub]);
        //  setImgBase64(newObj);
          // 파일 base64 상태 업데이트
        //  console.log(images)
        }
      }
    }
  }

  }

위 함수와 같이 event.target.files를 통해서 넘어오는 파일 배열을 받을 수 있다.

 

해당 데이터를 가지고 미리보기, 업로드의 구현을 해준다.

 

먼저 넘어 온 파일 배열을 setImgFile(event.target.files); 을 통해서 imgFile 스테이트에 저장한다.

 

그리고 아래와 같이 넘어온 파일을 반복문을 통해 하나씩 읽어 작업을 한다.

 for(var i=0;i<event.target.files.length;i++){
    if (event.target.files[i]) {
      let reader = new FileReader();
      reader.readAsDataURL(event.target.files[i]); // 1. 파일을 읽어 버퍼에 저장합니다.
      // 파일 상태 업데이트
      reader.onloadend = () => {
        // 2. 읽기가 완료되면 아래코드가 실행됩니다.
        const base64 = reader.result;
        console.log(base64)
        if (base64) {
        //  images.push(base64.toString())
        var base64Sub = base64.toString()
           
        setImgBase64(imgBase64 => [...imgBase64, base64Sub]);
        //  setImgBase64(newObj);
          // 파일 base64 상태 업데이트
        //  console.log(images)
        }
      }
    }
  }

 let reader = new FileReader(); 해당 파일 리더를 통해서 파일 정보를 읽을 수 있는데

 

 reader.readAsDataURL(event.target.files[i]); 해당 코드로 파일의 정보를 입력한다.

 

파일 읽기가 완료가 되면 아래 코드가 실행이 된다.

반응형
reader.onloadend = () => {
        // 2. 읽기가 완료되면 아래코드가 실행됩니다.
        const base64 = reader.result;
        console.log(base64)
        if (base64) {
        //  images.push(base64.toString())
        var base64Sub = base64.toString()
           
        setImgBase64(imgBase64 => [...imgBase64, base64Sub]);
        //  setImgBase64(newObj);
          // 파일 base64 상태 업데이트
        //  console.log(images)
        }
      }

reader.result 는 파일을 비트맵 데이터를 리턴해준다.

 

해당 데이터를 통해서 파일 미리보기가 가능하다.

비트맵 데이터를 저장 가능하도록 스트링으로 바꾼다.  

 var base64Sub = base64.toString()

해당 데이터를 imgBase64 스테이트에 저장하는데 파일이 하나가 아니라 배열이므로 배열에 붙여줘야한다.
 setImgBase64(imgBase64 => [...imgBase64, base64Sub]);

위와 같이 적으면 기존 imgBase64 배열에 base64Sub 값을 붙인 새로운 배열을 스테이트에 저장한다.

 

HTML에서 아래 코드로 imgBase64를 반복문을 돌려서 이미지의 비트맵을 읽어 src에 넣어주면 미리보기가 된다.

 

 {imgBase64.map((item) => {
       return(
  
        <img
          className="d-block w-100"
          src={item}
          alt="First slide"
          style={{width:"100%",height:"550px"}}
        />

       )
      }) }

 

이제 전송을 해야한다.

 

아래와 같이 글쓰기 버튼을 만든다.

 

 <button onClick={WriteBoard} style={{border:'2px solid black',width:'700px',fontSize:'40px'}}>작성완료</button>

writeBoard 함수를 실행하는 버튼이다.

 

const WriteBoard = async()=> {
    const fd = new FormData();
    Object.values(imgFile).forEach((file) => fd.append("file", file));
  
    fd.append(
      "comment",
      comment
    );

    await axios.post('http://localhost:8110/test/WriteBoard.do', fd, {
  headers: {
    "Content-Type": `multipart/form-data; `,
  }
})
.then((response) => {
   if(response.data){
       console.log(response.data)
    history.push("/test1");
  }
})
.catch((error) => {
  // 예외 처리
})
  } 

위와 같이 함수를 만들어 준다.

 

먼저 ajax에서 파일을 보내는 것과 같이 FormData를 사용한다.

 

해당 객체에  Object.values(imgFile).forEach((file) => fd.append("file", file)); 로 imgFile의 파일들을 읽어와 file이라는 이름으로 저장한다.

 

이렇게 저장을 하면 FormData에 file이라는 이름의 파일 배열이 들어간다.

 

그 외에 넣고 싶은 텍스트 등의 데이터는  fd.append( "comment",comment); 해당 코드와 같이 값을 넣어준다.

 

 

 await axios.post('http://localhost:8110/test/WriteBoard.do', fd, {
  headers: {
    "Content-Type": `multipart/form-data; `,
  }
})

위 코드와 같이 header을 파일 전송 형태로 해주고 경로에 fd(FormData)객체를 담아서 넘겨준다.

 

스프링에서는 해당 데이터를 아래와 같이 받아준다.

 

/**
	 * @param requestMap
	 * @return Map<String, Object>
	 * @throws SQLException
	 * @description 글 작성
	 */
	
	@RequestMapping(value="WriteBoard.do", method=RequestMethod.POST)
	public  Map<String,Object> WriteBoard (HttpServletRequest request,
			@RequestParam(value="file", required=false) MultipartFile[] files
			,@RequestParam(value="tag", required=false) String tag
            ,@RequestParam(value="comment", required=false) String comment) throws SQLException  {
		Map<String,Object> resultMap = new HashMap<String,Object>();
		String FileNames ="";
		System.out.println("paramMap =>"+files[0]);
		System.out.println("paramMap =>"+tag);
		System.out.println("paramMap =>"+comment);
		String filepath = "C:/cookingapp/churchfront/public/image/saveFolder/";
		   for (MultipartFile mf : files) {
			   
	            String originFileName = mf.getOriginalFilename(); // 원본 파일 명
	            long fileSize = mf.getSize(); // 파일 사이즈

	            System.out.println("originFileName : " + originFileName);
	            System.out.println("fileSize : " + fileSize);

	            String safeFile =System.currentTimeMillis() + originFileName;
	            
	            FileNames = FileNames+","+safeFile; 
	            try {
	            	File f1 = new File(filepath+safeFile);
	                mf.transferTo(f1);
	            } catch (IllegalStateException e) {
	                // TODO Auto-generated catch block
	                e.printStackTrace();
	            } catch (IOException e) {
	                // TODO Auto-generated catch block
	                e.printStackTrace();
	            }
	        }
		Map<String, Object> paramMap = new HashMap<String, Object>();
		FileNames = FileNames.substring(1);
		System.out.println("FileNames =>"+ FileNames);
		paramMap.put("comment", comment);
		paramMap.put("FileNames", FileNames);
		paramMap.put("tag", tag);
		resultMap.put("JavaData", paramMap);
		return resultMap;
	}

위와 같이 작성을 하면 리액트 - 스프링간에 파일 데이터 전송이 완료된다.

 

 

=====================

코드 전체

import React, { useState, useEffect } from 'react';
import Carousel from 'react-bootstrap/Carousel'
import 'bootstrap/dist/css/bootstrap.min.css';
import '../../resources/Css/InputDesign.css';
import '../../resources/Css/Mainboard.css';
import axios,{ post } from 'axios';
import DetailPopup from './DetailPopup';
import { Button,InputGroup ,FormControl  } from 'react-bootstrap';
import { IoRemoveCircle } from "react-icons/io5";
import {useHistory} from "react-router-dom"
function WriteMain() {
  let history = useHistory(); 
  const [imgBase64, setImgBase64] = useState([]); // 파일 base64
  const [imgFile, setImgFile] = useState(null);	//파일	
  const [tag, setTag] = useState([]);
  const [comment,setComment] = useState();
  var images = []
 
  const handleChangeFile = (event) => {
    console.log(event.target.files)
    setImgFile(event.target.files);
    //fd.append("file", event.target.files)
    setImgBase64([]);
    for(var i=0;i<event.target.files.length;i++){
    if (event.target.files[i]) {
      let reader = new FileReader();
      reader.readAsDataURL(event.target.files[i]); // 1. 파일을 읽어 버퍼에 저장합니다.
      // 파일 상태 업데이트
      reader.onloadend = () => {
        // 2. 읽기가 완료되면 아래코드가 실행됩니다.
        const base64 = reader.result;
        if (base64) {
        //  images.push(base64.toString())
        var base64Sub = base64.toString()
           
        setImgBase64(imgBase64 => [...imgBase64, base64Sub]);
        //  setImgBase64(newObj);
          // 파일 base64 상태 업데이트
        //  console.log(images)
        }
      }
    }
  }

  }
  const resetTag  = () => {
    document.getElementById('innerinput').value = '';
  }
  useEffect(resetTag, [tag])

  const setTags = (e) => {
    if(e.key == ' ' || e.key == 'Enter'){
      if(document.getElementById('innerinput').value != ''){
        setTag(tag => [...tag, e.target.value]);
      }else{
        alert("태그를 입력해주세요")
        return false;
      }
     
     
    }
  }
  const deleteTag = (index) => {
    var array = [...tag];
    array.splice(index, 1);
    setTag(array)
  }

  const setComments = (e) => {
    setComment(e.target.value)
  }

  const WriteBoard = async()=> {
    if(imgFile == null){
      alert("이미지를 등록 해주세요");
      return false;
    }
    const fd = new FormData();
    Object.values(imgFile).forEach((file) => fd.append("file", file));
  
    fd.append(
      "tag",
    tag
    );
    fd.append(
      "comment",
      comment
    );

   
    // axios(
    //   {
    //     url: '/board/WriteBoard.do',
    //     method: 'post',
    //     headers: {
    //       "Content-Type": `multipart/form-data`,
    //     },
    //     data: fd , 
    //     baseURL: 'http://localhost:8080'
    //     //withCredentials: true,
    //   }
    // ).then(function (response) {
    //  console.log(response)
    // });
    const fd2 = new FormData();
    await axios.post('http://localhost:8080/board/WriteBoard.do', fd, {
  headers: {
    "Content-Type": `multipart/form-data; `,
  }
})
.then((response) => {
   if(response.data){
    history.push("/MainBoard");
  }
})
.catch((error) => {
  // 예외 처리
})
  } 
    return (
      <div class="FlexRow_c">
      <div class="FlexCol_c">
        <input type="file" id="file" style={{display:'none'}} onChange={handleChangeFile} multiple="multiple" />
        <label for="file" class="FlexCol_c" style={{border:'2px solid black',width:'700px',height:'300px',marginTop:'100px',fontSize:'40px'}}><strong>FILE UPLOAD <br/> Click here!</strong></label>
        <div  class="outer-input FlexRow_ac" style={{minHeight:'70px',width:'700px',padding:'0px',textAlign:'left',overflow:'auto'}}>
        <input class="innerinput" id="innerinput" style={{border:'0px',height:'30px',width:'120px',paddingLeft:'12px'}} placeholder="#tag" onKeyPress={setTags}></input>
          {tag.map(((item,index) => {
              return (<span><span class='tag'>{item}</span><IoRemoveCircle onClick={() => deleteTag(index)} class="delete"  style={{color:"red",fontSize:"20px"}}/></span>)
          }))}
        </div>
        <textarea name="text" class="feedback-input borderBox" id="comment" placeholder="Comment" style={{resize:'none'}} onChange={setComments}></textarea>
        <button onClick={WriteBoard} style={{border:'2px solid black',width:'700px',fontSize:'40px'}}>작성완료</button>
      </div>
      <div class="borderBox" style={{width:'700px', height:"560px",marginTop:'100px',marginLeft:'60px',border:'2px solid black'}}>
      <Carousel interval={null}>
      {imgBase64.map((item) => {
       return(
        <Carousel.Item>
        <img
          className="d-block w-100"
          src={item}
          alt="First slide"
          style={{width:"100%",height:"550px"}}
        />
        <Carousel.Caption>
          <h3>First slide label</h3>
          <p>Nulla vitae elit libero, a pharetra augue mollis interdum.</p>
        </Carousel.Caption>
      </Carousel.Item>
       )
      }) }
</Carousel>
      </div>
      </div>
    );
  }
  

  export default WriteMain;  

 

반응형