์ผ | ์ | ํ | ์ | ๋ชฉ | ๊ธ | ํ |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- K๋ฐฐํฐ๋ฆฌ
- ์ฑ
- ๋ผํ๋ผ์ค์๋ง๋
- html
- Java
- ๋ ์
- ๋ง์ผ๋ด๊ฐ์ธ์์๋ค์์ฐ๋ค๋ฉด
- css
- database
- ์ค๋ธ์
- ํ๋ก๊ทธ๋๋ฐ
- ๋ฆฌ์กํธ
- ํ์ด์ฌ
- ๋ฐ์ํ
- ์ปดํจํฐ๊ณผํ
- ํฐ์คํ ๋ฆฌ์ฑ๋ฆฐ์ง
- ์๋ฐ
- ์ํ
- ์นํ์ด์ง๋ง๋ค๊ธฐ
- ์นํผ๋ธ๋ฆฌ์ฑ
- K๋ฐฐํฐ๋ฆฌ๋ ๋ณผ๋ฃจ์
- ์ค๋ผํด
- JavaScript
- ์๋ฐ์คํฌ๋ฆฝํธ
- ๊น๋ฏธ๊ฒฝ์๋งํ์์
- ๋ฐ์ดํฐ๋ฒ ์ด์ค
- ComputerScience
- Python
- ์ฝ๋ฉ
- ๊ฐ๋ฐ
- Today
- Total
JiYoung Dev ๐ฅ
[React] ๋๋๊ทธ ์ค ๋๋กญ ํ์ผ ์ ๋ก๋ ๊ธฐ๋ฅ ๊ตฌํ ๋ณธ๋ฌธ
[React] ๋๋๊ทธ ์ค ๋๋กญ ํ์ผ ์ ๋ก๋ ๊ธฐ๋ฅ ๊ตฌํ
Shinjio 2024. 11. 5. 20:19์ค๋์ ๋ฆฌ์กํธ๋ฅผ ์ด์ฉํด ๋ก์ปฌ์ ์กด์ฌํ๋ ์ฒจ๋ถํ์ผ์ ์ ๋ก๋ํ์ฌ API๋ก ์ ์กํ๋ ๊ธฐ๋ฅ์ ๊ตฌํํด ๋ณด์๋ค.
<input type="file"> ํ๊ทธ
๋จผ์ , ๋ฆฌ์กํธ์์ ์ฒจ๋ถํ์ผ์ ์ ๋ก๋ ํ๊ธฐ ์ํด์๋ <input type="file"> ํ๊ทธ๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
ํด๋น ํ๊ทธ๋ HTML ๊ธฐ๋ณธ ํ๊ทธ๋ก ๋ก์ปฌ ์ปดํจํฐ์ ์กด์ฌํ๋ ํ์ผ์ ๊ฐ์ ธ ์ฌ ์ ์๋๋ก ๋์์ฃผ๋ ํ๊ทธ์ด๋ค.
๋ง์ฝ ์๋ฌด ์ค์ ์์ด ํด๋น ํ๊ทธ๋ง ์ฌ์ฉํ๋ค๋ฉด ์๋์ ๊ฐ์ด ๋ณด์ฌ์ค๋ค.
ํ์ผ ์ ํ์ด๋ผ๋ ๋ฒํผ๊ณผ ํ ์คํธ๊ฐ ๋จ๊ณ , 'ํ์ผ ์ ํ' ๋ฒํผ์ ํด๋ฆญํ๋ฉด ์ฐ๋ฆฌ๊ฐ ์ ์๊ณ ์๋ ํ์ผ์ ์ ๋ก๋ ํ๋ ์ฐฝ์ด ๋ฌ๋ค.
์ด๋ ๊ฒ ๊ฐ๋จํ๊ฒ input ํ๊ทธ์ file ์์ฑ๋ง์ผ๋ก๋ ํ์ผ ์ ๋ก๋๋ฅผ ํ ์ ์์ง๋ง ์กฐ๊ธ ๋ ์ฌ์ฉ์๊ฐ ํธํ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก drag & drop ๊ธฐ๋ฅ์ ์ถ๊ฐํ ์ ์๋ค.
<label>์ ์ฌ์ฉํ drag & drop ๊ธฐ๋ฅ ์ถ๊ฐ
๋ค์ด๊ฐ๊ธฐ์ ์์ label ํ๊ทธ์ ๋ํด ์์๋ณด์.
label ํ๊ทธ๋ for ์์ฑ์ ์ฌ์ฉํ์ฌ ๋ค๋ฅธ ์์์ ๊ฒฐํฉ์ด ๊ฐ๋ฅํ ํ๊ทธ์ด๋ค.
์๋ฅผ๋ค์ด, <input type="radio"> ํ๊ทธ์ ๊ฒฝ์ฐ ๋ผ๋์ค ๋ฒํผ๋ง ํํ๋์ง ์ด๋ ํ ํ ์คํธ๋ ํ์ํ์ง ์๋๋ค.
์ด ๋๋ label ํ๊ทธ๋ฅผ ํจ๊ป ์ฌ์ฉํ์ฌ ๋ผ๋์ค ๋ฒํผ๊ณผ ํจ๊ป ํด๋น ๋ฒํผ์ด ๊ฐ์ง๋ ํ ์คํธ ์์๋ฅผ ํจ๊ป ํ์ํ ์ ์๋ค.
<label> ํ๊ทธ๋ for ์์ฑ์ ์ฌ์ฉํ์ฌ ๋ค๋ฅธ ์์์ ๊ฒฐํฉํ ์ ์๊ณ , ์ด ๋ for ์์ฑ ๊ฐ์ ๊ฒฐํฉํ๊ณ ์ ํ๋ ์์์ id ์์ฑ ๊ฐ๊ณผ ๊ฐ์์ผ ํ๋ค. ๋ํ, ๊ฒฐํฉ์ํค๊ณ ์ ํ๋ ์์ ๋ด์ <label> ํ๊ทธ๋ฅผ ์์น ์ํค๋ฉด for ์์ฑ์ ์ฌ์ฉํ์ง ์๋๋ผ๋ ์์์ ๊ฒฐํฉํ ์ ์๋ค.
<label> ํ๊ทธ๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ ์ฌ์ฉ์๊ฐ ๋ผ๋ฒจ ํ ์คํธ๋ฅผ ํด๋ฆญํ๋ฉด ๋ผ๋ฒจ๊ณผ ์ฐ๊ฒฐ๋ ์์๋ฅผ ๊ณง๋ฐ๋ก ์ ํํ ์ ์์ด ์ฌ์ฉ์ ํธ์์ฑ์ ๋์ผ ์ ์๋ค. <label> ํ๊ทธ๋ <button>, <input>, <select>, <testarea>,<meter>,<output>,<progress>์ ํจ๊ป ์ฌ์ฉํ ์ ์๋ค.
<input type="file">์์๋ <label> ํ๊ทธ์ ํจ๊ป ์ฌ์ฉํ์ฌ label ์์ ํด๋ฆญ ์ ํ์ผ ์ ๋ก๋ ์ฐฝ์ด ๋จ๋๋ก ๋ง๋ค ์ ์๋ค.
<label for="input-file">
<p>ํด๋ฆญ ํน์ ํ์ผ์ ์ด๊ณณ์ ๋๋กญํ์ธ์.</p>
<p>ํ์ผ๋น ์ต๋ 3MB</p>
</label>
<input id="input-file" type="file"></input>
๊ทธ๋ฆฌ๊ณ input์ ๋ํ css๋ display="none"์ ์ ์ฉ์์ผ ๋ฒํผ์ ๋ณด์ด์ง ์๊ฒ ๋ง๋ค๊ณ ,
label์ ๋ํ css๋ฅผ ์ ์ฉํ์ฌ ๋ผ๋ฒจ์ ํด๋ฆญ ํ์ ๋ ํ์ผ ์ ๋ก๋ ์ฐฝ์ด ๋จ๋๋ก ๋ง๋ค ์ ์๋ค.
๊ทธ๋ฆฌ๊ณ drag & drop ๊ธฐ๋ฅ์ ์ ์ฉํ๊ธฐ ์ํด์ label์ drag์ drop์ ๋ํ ์์ฑ์ ์ด์ฉํ๋ค.
๋๋๊ทธ & ๋๋กญ ์ด๋ฒคํธ ์ข ๋ฅ
HTML ์์์์ ๋๋๊ทธ ์ค ๋๋กญ์ ์ผ์ด๋๋ ์ด๋ฒคํธ๋ฅผ ์์๋๋ก ๋์ดํ๋ฉด ์๋์ ๊ฐ๋ค.
dragstart | 1. ์ฌ์ฉ์๊ฐ ๊ฐ์ฒด๋ฅผ ๋๋๊ทธํ๋ ค๊ณ ์์ํ ๋ ๋ฐ์ |
drag | 2. ๋์ ๊ฐ์ฒด๋ฅผ ๋๋๊ทธํ๋ฉด์ ๋ง์ฐ์ค๋ฅผ ์์ง์ผ ๋ ๋ฐ์ |
dragenter | 3. ๋ง์ฐ์ค๊ฐ ๋์ ๊ฐ์ฒด์ ์๋ก ์ฒ์ ์ง์ ํ ๋ ๋ฐ์ |
dragover | 4. ๋๋๊ทธํ๋ฉด์ ๋ง์ฐ์ค๊ฐ ๋์ ๊ฐ์ฒด์ ์์ญ ์์ ์๋ฆฌ ์ก๊ณ ์์ ๋ ๋ฐ์ |
drop | 5. ๋๋๊ทธ๊ฐ ๋๋์ ๋๋๊ทธํ๋ ๊ฐ์ฒด๋ฅผ ๋๋ ์ฅ์์ ์์นํ ๊ฐ์ฒด์์ ๋ฐ์. ๋ฆฌ์ค๋๋ ๋๋๊ทธ ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์์ ๋๋กญ ์์น์ ๋๋ ์ญํ ์ ํจ |
dragleave | 6. ๋๋๊ทธ๊ฐ ๋๋์ ๋ง์ฐ์ค๊ฐ ๋์ ๊ฐ์ฒด์ ์์์ ๋ฒ์ด๋ ๋ ๋ฐ์ |
dragend | 7. ๋์ ๊ฐ์ฒด๋ฅผ ๋๋๊ทธํ๋ค๊ฐ ๋ง์ฐ์ค ๋ฒํผ์ ๋๋ ์๊ฐ ๋ฐ์ |
ํ์ผ ์ ๋ก๋ ๊ธฐ๋ฅ์ ๋๋๊ทธ ์ค ๋๋กญ์ ์ถ๊ฐํ๊ธฐ ์ํด์๋
dragenter, dropover, drop, dragleave ์์ฑ์ ์ฌ์ฉํ๋ค.
<label
className={`preview${isActive ? " active" : ""}`}
for="input-file"
onDragEnter={handleDragStart}
onDragLeave={handleDragEnd}
onDragOver={handleDragOver}
onDrop={handleDrop}
>
{uploadedInfo && <FileInfo uploadedInfo={uploadedInfo} />}
{!uploadedInfo && (
<>
<p className="preview-msg">ํด๋ฆญ ํน์ ํ์ผ์ ์ด๊ณณ์ ๋๋กญํ์ธ์.</p>
<p className="preview-desc">ํ์ผ๋น ์ต๋ 3MB</p>
</>
)}
</label>
const [isActive, setActive] = useState(false);
const [uploadedInfo, setUploadedInfo] = useState(null);
const [file, setFile] = useState(null);
const handleDragStart = () => setActive(true);
const handleDragEnd = () => setActive(false);
const handleDrop = (event) => {
event.preventDefault();
const file = event.dataTransfer.files[0];
setFileInfo(file);
setActive(false);
};
const handleDragOver = (e) => {
e.preventDefault();
};
const handleUpload = ({ target }) => {
const file = target.files[0];
setFileInfo(file);
};
dragstart๋ ์ฌ์ฉ์๊ฐ ๋๋๊ทธ ํ์ฌ label ํ๊ทธ ์์ผ๋ก ๋ง์ฐ์ค ์ปค์๊ฐ ์ฒ์ ๋ค์ด์จ ์๊ฐ ๋ํ๋๋ ์ด๋ฒคํธ๋ก ํด๋น ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ ๋์๋ active ์ํ๋ฅผ ๋ณ๊ฒฝํ์ฌ css ์์๋ฅผ ๋ณ๊ฒฝํ ์ ์๋๋ก ํ์๋ค.
dragover๋ ๋๋๊ทธํ๋ฉด์ ๋ง์ฐ์ค๊ฐ label ๊ฐ์ฒด ์์ญ ์์ ์์ ๋ ๋ฐ์ํ๋ ์ด๋ฒคํธ๋ก ํด๋น ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ ๋์๋ e.preventDefault()๋ฅผ ํตํด ๊ธฐ์กด์ ์กด์ฌํ๋ ์ด๋ฒคํธ ๋ฐ์์ ๋ง๋๋ค.
๋ง์ฝ ํด๋น ์ฝ๋๊ฐ ์๋ค๋ฉด ๋๋๊ทธ ์ค ๋๋กญ์ ์๋์ผ๋ก ํ์ผ ๋ค์ด์ด ์์๋๋ค.
drop์ ๋๋๊ทธ๊ฐ ๋๋์ ๋๋๊ทธํ๋ ๊ฐ์ฒด๋ฅผ ํด๋น ์์ญ์์ ๋์์ ๋ ๋ฐ์ํ๋ ์ด๋ฒคํธ๋ก ํด๋น ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ ๋์๋ e.preventDefault()๋ฅผ ํตํด ๊ธฐ์กด ์ด๋ฒคํธ ๋ฐ์์ ๋ง๊ณ , drop ์ด๋ฒคํธ์์ ๋ฐํ๋๋ DataTransfer ๊ฐ์ฒด๋ฅผ ํตํด ์ ํํ ํ์ผ์ ๋ํ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์จ๋ค.
dragleave๋ ๋๋๊ทธํ ์ํ์์ label ํ๊ทธ์์ ๋ง์ฐ์ค๊ฐ ๋ฒ์ด๋ฌ์ ๋ ๋ํ๋๋ ์ด๋ฒคํธ๋ก dragstart์ ๋ง์ฐฌ๊ฐ์ง๋ก active ์ํ๋ฅผ ๋ณ๊ฒฝํ์ฌ css ์์๋ฅผ ๋ณ๊ฒฝํ๋ค.
๋๋๊ทธ ์ค ๋๋กญ ์ด๋ฒคํธ๋ฅผ ์ํ ๋ชจ๋ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฉ์๋๋ DataTransfer ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค.
DataTransfer ๊ฐ์ฒด๋ฅผ ํตํด ์ด๋ฆ, ํฌ๊ธฐ, ํ์ ๋ฑ ํด๋น ๊ฐ์ฒด์ ์ ๋ณด๋ฅผ ์ป์ ์ ์๋ค.
const setFileInfo = (file) => {
const { name, size: byteSize, type } = file;
const size = (byteSize / (1024 * 1024)).toFixed(2) + "mb";
console.log(name);
console.log(size);
console.log(type);
setUploadedInfo({ name, size, type });
setFile(file);
};
const FileInfo = ({ uploadedInfo }) => (
<ul className="preview-info">
{Object.entries(uploadedInfo).map(([key, value]) => (
<li key={key}>
<span className="info-key">{key}</span>
<span className="info-value">{value}</span>
</li>
))}
</ul>
);
<>
<label
className={`preview${isActive ? " active" : ""}`}
for="input-file"
onDragEnter={handleDragStart}
onDragLeave={handleDragEnd}
onDragOver={handleDragOver}
onDrop={handleDrop}
>
{uploadedInfo && <FileInfo uploadedInfo={uploadedInfo} />}
{!uploadedInfo && (
<>
<p className="preview-msg">ํด๋ฆญ ํน์ ํ์ผ์ ์ด๊ณณ์ ๋๋กญํ์ธ์.</p>
<p className="preview-desc">ํ์ผ๋น ์ต๋ 3MB</p>
</>
)}
</label>
<input id="input-file" type="file" onChange={handleUpload}></input>
<button onClick={uploadFile}>์ ์กํ๊ธฐ</button>
<button onClick={() => setUploadedInfo(null)}>์ทจ์ํ๊ธฐ</button>
</>
Axios๋ก ์ ๋ก๋ํ file ๋ณด๋ด๊ธฐ
๋ง์ง๋ง์ผ๋ก ํ์ผ ์ ๋ก๋์์ ์ ํํ ํ์ผ์ axios๋ฅผ ํตํด ๋ฐฑ์๋๋ก ์์ฒญ์ ๋ณด๋ผ ์ ์๋ค.
ํ์ผ์ ๋ณด๋ด๊ธฐ ์ํด์๋ FormData๋ผ๋ ๊ฐ์ฒด ํํ๋ฅผ ์ฌ์ฉํ์ฌ ๋ณด๋ธ๋ค.
formData ๊ฐ์ฒด๋ XMLHttpRequest ์ ์ก์ ์ํ์ฌ ์ค๊ณ๋ ํน์ํ ๊ฐ์ฒด ํํ๋ก ํน์ ํ ์กฐ์์ ๊ฐํ์ง ์์ผ๋ฉด ๋ฌธ์์ดํ๊ฐ ๋ถ๊ฐ๋ฅํ๋ค. formData ๊ฐ์ฒด๋ key, value ํ์์ผ๋ก ๋์ด ์์ผ๋ฉฐ, multipart/form-data ํ์์ผ๋ก ์ ์กํ ์ ์๋ค.
const uploadFile = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("files", file);
axios
.post("http://localhost:8080/file/uploaded", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((res) => {
console.log(res.data);
})
.catch((err) => {
console.log(err);
});
};
์ ์ฒด ์ฝ๋
import "./App.css";
import { useState } from "react";
import axios from "axios";
function App() {
const FileInfo = ({ uploadedInfo }) => (
<ul className="preview-info">
{Object.entries(uploadedInfo).map(([key, value]) => (
<li key={key}>
<span className="info-key">{key}</span>
<span className="info-value">{value}</span>
</li>
))}
</ul>
);
const UploadBox = () => {
const [isActive, setActive] = useState(false);
const [uploadedInfo, setUploadedInfo] = useState(null);
const [file, setFile] = useState(null);
const setFileInfo = (file) => {
const { name, size: byteSize, type } = file;
const size = (byteSize / (1024 * 1024)).toFixed(2) + "mb";
console.log(name);
console.log(size);
console.log(type);
setUploadedInfo({ name, size, type });
setFile(file);
};
const handleDragStart = () => setActive(true);
const handleDragEnd = () => setActive(false);
const handleDrop = (event) => {
event.preventDefault();
const file = event.dataTransfer.files[0];
setFileInfo(file);
setActive(false);
};
const handleDragOver = (e) => {
e.preventDefault();
};
const handleUpload = ({ target }) => {
const file = target.files[0];
setFileInfo(file);
};
const uploadFile = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("files", file);
axios
.post("http://localhost:8080/file/uploaded", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((res) => {
console.log(res.data);
})
.catch((err) => {
console.log(err);
});
};
return (
<>
<label
className={`preview${isActive ? " active" : ""}`}
for="input-file"
onDragEnter={handleDragStart}
onDragLeave={handleDragEnd}
onDragOver={handleDragOver}
onDrop={handleDrop}
>
{uploadedInfo && <FileInfo uploadedInfo={uploadedInfo} />}
{!uploadedInfo && (
<>
<p className="preview-msg">ํด๋ฆญ ํน์ ํ์ผ์ ์ด๊ณณ์ ๋๋กญํ์ธ์.</p>
<p className="preview-desc">ํ์ผ๋น ์ต๋ 3MB</p>
</>
)}
</label>
<input id="input-file" type="file" onChange={handleUpload}></input>
<button onClick={uploadFile}>์ ์กํ๊ธฐ</button>
<button onClick={() => setUploadedInfo(null)}>์ทจ์ํ๊ธฐ</button>
</>
);
};
return (
<div className="App">
<UploadBox />
</div>
);
}
export default App;
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
#input-file {
display: none;
}
#input-file::file-selector-button {
font-size: 14px;
background: #fff;
border: 1px solid #111;
border-radius: 12px;
padding: 4px 32px;
cursor: pointer;
}
.preview {
width: 300px;
height: 150px;
margin: auto;
background-color: #fff;
border-radius: 5px;
border: 3px dashed #eee;
padding: 70px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
cursor: pointer;
}
.preview.active {
background-color: #efeef3;
border-color: #111;
}
.preview:hover {
border-color: #111;
}
.preview-msg {
font-weight: 500;
font-size: 18px;
margin: 20px 0 10px;
}
.preview-desc {
margin: 0;
font-size: 14px;
}
.preview-info {
width: 100%;
list-style: none;
padding: 0;
gap: 16px;
display: flex;
flex-direction: column;
}
.preview-info .info-key {
display: block;
font-weight: 500;
font-size: 12px;
margin-bottom: 4px;
}
.preview-info .info-value {
font-size: 14px;
}