์ค๋์ ์ด๋ฏธ์ง๋ฅผ ์ ์ฅํ๊ณ ๋ถ๋ฌ์ค๋ ๊ณผ์ ์ ๋ํด ๊ณต๋ถํ๋ค.
์ด๋ฏธ์ง๋ ์คํ ๋ฆฌ์ง ์ปดํจํฐ
๋ผ๋ ์ปดํจํฐ์ ๋ฐ๋ก ์ ์ฅํ๋ค. ์คํ ๋ฆฌ์ง๋ ์ฌ๋ฌ ์ปดํจํฐ๋ค์ ์ฐ๊ฒฐ์์ผ ๋์ ํฐ ์ฉ๋์ ๋ด์ ์ ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ค.
์คํ ๋ฆฌ์ง
๋ ๋น์ฉ์ด ๋ง์ด ๋ค๊ธฐ ๋๋ฌธ์ ์คํ ๋ฆฌ์ง๋ฅผ ์ ๊ณต(cloud provider)ํ๋ ํด๋ผ์ฐ๋ ์ ๊ณต์
์ฒด(ex. AWS, GCP, Azure...)๊ฐ ๋ฑ์ฅํ๋ค.
๋ธ๋ผ์ฐ์ ์์ api๋ฅผ ์์ฒญํ ๊ฒฝ์ฐ ์ด๋ฏธ์ง ์ฃผ์๋ฅผ ๋ฐฑ์๋๋ก ๋ณด๋ด๊ณ , ๋ฐฑ์๋๋ ํ์ผ์ ์คํ ๋ฆฌ์ง ์ปดํจํฐ๋ก ๋ณด๋ธ๋ค. ๊ทธ๋ฆฌ๊ณ ์คํ ๋ฆฌ์ง ์ปดํจํฐ์์ ์ฃผ์๋ฅผ ๋ค์ ๋ฌธ์์ด๋ก ๋ฐ์์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ๊ฒ ๋๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ ์๋ ์ด๋ฏธ์ง์ ์ฃผ์๋ฅผ ๋ฐ์์์ img tag์ ๋ฃ์ด์ฃผ๋ฉด, ํด๋น ์คํ ๋ฆฌ์ง์ ์ ์ํด์ ์ด๋ฏธ์ง(ํ์ผ)๋ฅผ ๋ค์ด๋ก๋ ๋ฐ์์จ๋ค.
์ฐธ๊ณ ! ์ด๋ฏธ์ง๋ฅผ ํฌ๊ฒ ํ๋ํด๋ณด๋ฉด ํฝ์ ์ด ํ๋ํ๋ ๋ชจ์ฌ์๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ํฝ์ ํ ์นธ์ ๋น์ 3์์(RGB)๋ฅผ ์์ด์ ๋ง๋ค์ด์ก๋ค. ๊ฐ ํฝ์ ์ 3๊ฐ์ง ์ซ์๋ก ๊ตฌ์ฑ๋๋ค.
R(0~255)
G(0~255)
B(0~255)
createUploadLink
๋ฅผ ์ค์นyarn add apollo-upload-client
yarn add --dev @types/apollo-upload-client
// createUploadLink import
import {createUploadLink} from "apollo-upload-client"
// ํจ์ ๋ง๋ค๊ธฐ
const uplodLink = createUploadLink({
uri : "๋ฐฑ์๋ ์ฃผ์"
})
const client = new ApolloClient({
link : ApolloLink.from([uplodLink]),
cache : new inMemoryCache(),
})
// ์ด๋ฏธ์ง ์
๋ก๋ api ์ฌ์ฉ์ ์ํ ์ฟผ๋ฆฌ ์์ฑ
const UPLOAD_FILE = gql`
mutation uploadFile($file: Upload!) {
uploadFile(file: $file) {
url
}
}
`;
export default function ImageUploadPage(): JSX.Element {
const [imageUrl, setImageUrl] = useState("");
const [uploadFile] = useMutation<
Pick<IMutation, "uploadFile">,
IMutationUploadFileArgs
>(UPLOAD_FILE);
// ์ด๋ฏธ์ง ์
๋ก๋ ํจ์
const onChangeFile = async (
event: ChangeEvent<HTMLInputElement>
): Promise<void> => {
const file = event.target.files?.[0];
// ๋ฐฐ์ด์ ๋ฐ์์ค๋ ์ด์ : <input type="file" multiple/>์ผ๋, ์ฌ๋ฌ ๊ฐ ์
๋ก๋ ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ!
const result = await uploadFile({ variables: { file } })
setImageUrl(result.data?.uploadFile.url ?? "");
};
return (
<>
// input type file๋ก ์ง์
<input onChange={onChangeFile} type="file" />
<img src={`https://storage.googleapis.com/${imageUrl}`} />
</>
);
์ด๋ฏธ์ง๋ฅผ ๋ณด๋ด๊ณ , url์ ๋ฐ์์ค๋ ๊ณผ์
1.onChange
๋ฅผ ํตํด ์ด๋ฏธ์ง ํ์ผ์ ๊ฐ์ง๊ณ ์ file์ ๋ฃ์ด์ค
2. file์variables
์ ๋ฃ์ด uploadFile api ์์ฒญ
3. ์ด๋ฏธ์ง๊ฐ ์ ํด๋ storage์ ๋ค์ด๊ฐ๊ณ , ํด๋น storage์์ url ๋ฐํ ๋ฐ์์ด
๊ธฐ๋ณธ input tag๋ ์์์ง ์๋ค.. ์ข ๋ ์์๊ฒ UI๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํด์๋ input
ํ๊ทธ๋ฅผ ์จ๊ธฐ๊ณ ๋ค์์ 2๊ฐ์ง ๋ฐฉ๋ฒ ์ค ์ํฉ์ ๋ง๋ ๋ฐฉ์์ ํ์ฉํ๋ฉด ๋๋ค!
์ฐ๋ฆฌ๊ฐ HTML ํ๊ทธ๋ฅผ ์ ํํ ๋๋ getElementId๋ฅผ ์ฌ์ฉํ๋ค. react์์๋ HTML ํ๊ทธ์ ์ ๊ทผํ๊ธฐ ์ํด useRef๋ฅผ ์ฌ์ฉํ๋ค.
// useRef import
import { useRef } from 'react';
export default function ImageRefPage(): JSX.Element {
// ref ๊ธฐ๋ณธ ์ค์
// fileRef๋ฅผ input ํ๊ทธ์ ์ฐ๊ฒฐํด์ฃผ๋ฉด ํด๋น ํ๊ทธ๋ fileRef๋ก ๋ถ๋ฌ์ ์ฌ์ฉ ๊ฐ๋ฅ!
const fileRef = useRef<HTMLInputElement>(null);
}
return (
<>
<input
style={{ display: "none" }}
onChange={onChangeFile}
type="file"
// input ํ๊ทธ๋ฅผ fileRef๋ฅผ ์ด์ฉํด ์ฌ์ฉ ๊ฐ๋ฅ
ref={fileRef}
/>
</>
)
์ด์ useRef์ ๊ธฐ๋ฅ ์ค current
๋ฅผ ์ฌ์ฉํด์ current ์์ click์ด๋ผ๋ ๊ธฐ๋ฅ์ onClickImage ํจ์์ ๋ฃ์ด์ค๋ค. current๋ fileRef์ ๋ค์ด์จ ํ๊ทธ๋ฅผ ๋ปํ๊ณ , ๊ทธ ํ๊ทธ๋ฅผ clickํ๊ฒ ๋ค๋ ๊ธฐ๋ฅ์ด๋ค!
const onClickImage = (): void => {
// ๊ธฐ์กด ๋ฐฉ์: document.getElementById("ํ์ผํ๊ทธID")?.click();
fileRef.current?.click();
};
์ด์ button
tag๋ฅผ ๋ง๋ค์ด onClickImage ํจ์์ ๋ฐ์ธ๋ฉ์์ผ์ฃผ๋ฉด ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด button์ ํด๋ฆญํ์ ๋ fileRef.current.click()
์ด ์คํ๋๊ณ , ์ฐ๋ฆฌ๊ฐ useRef์ ๋ฃ์ด๋์๋ input ํ๊ทธ๋ฅผ ํด๋ฆญํ ๊ฒ๊ณผ ๋์ผํ ๊ฒฐ๊ณผ๊ฐ ์คํ๋๋ค!
<button onClick={onClickImage}>์ด๋ฏธ์ง ๋ฑ๋ก ๋ฒํผ</button>
์ด์ img ํ๊ทธ์๋ onClickImage ํจ์๋ฅผ ๋ฐ์ธ๋ฉํ๊ณ , input ํ๊ทธ์ ์์ฑ์ผ๋ก hidden
์ ๋ฃ์ด์ input ํ๊ทธ๋ ๋ธ๋ผ์ฐ์ ์ ๋ํ๋์ง ์๋๋ก ํ๊ณ , img์ button์ ํด๋ฆญํ์ ๋ input type=file
์ ํด๋ฆญํ ๊ฒ๊ณผ ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ๋ณผ ์ ์๋ค.
return (
<>
<div>
<img onClick={onClickImage} style={{ width: '500px' }} id="image" />
<input
hidden={true}
ref={fileRef}
type="file"
onChange={readImage}
></input>
<button onClick={onClickImage}>์ด๋ฏธ์ง ๋ฑ๋ก ๋ฒํผ</button>
</div>
</>
);
useRef ์ธ์ input ํ๊ทธ๋ฅผ ์จ๊ธฐ๊ณ UI๋ฅผ ๊ฐ์ ํ๋ ๋ฐฉ๋ฒ์ label
ํ๊ทธ๋ฅผ ์ด์ฉํ๋ ๊ฒ์ด๋ค.
label ํ๊ทธ์๋ htmlFor
์ด๋ผ๋ ์์ฑ์ด ์๋ค. htmlFor์ ๊ฐ์ ๋ฃ์ผ๋ฉด ๊ฐ๊ณผ ๋๊ฐ์ id๋ฅผ ์ฐพ์์ ๊ทธ ํ๊ทธ์ ๊ธฐ๋ฅ๊ณผ ์ฐ๊ฒฐํด์ค๋ค.
์๋ ์ฝ๋์ ๊ฐ์ด ์ฌ์ฉํ ์ ์๋ค.
<div>
<label htmlFor="fileTag">์ด๊ฑฐ ๋๋ฌ๋ ์คํ๋ผ์!</label>
<img style={{ width: '500px' }} id="image" />
<input id="fileTag" type="file" onChange={readImage}></input>
</div>
์ด์ label ํ๊ทธ๋ฅผ ์ฐ๋ฆฌ๊ฐ ์ํ๋ ์์ ๋์์ธ์ผ๋ก ๋ฐ๊พธ๊ณ , ๊ธฐ์กด input ํ๊ทธ๋ ์ ๋ณด์ด๋๋ก CSS๋ฅผ ์์ ํ๋ฉด ๋๋ค.
์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ํ ๋ ์ฉ๋์ ์ ํํ๊ฑฐ๋ ํ์ผ ํ์ฅ์๋ฅผ ์ ํ ๋ฐ ๊ฒ์ฆํ๋ ๊ณผ์ ์ด ํ์ํ ๋์๋ ์ด๋ป๊ฒ ํด์ผ ํ ๊น?
const onChangeFile = async (
event: ChangeEvent<HTMLInputElement>
): Promise<void> => {
const file = event.target.files?.[0];
// ๊ฒ์ฆ ๋ก์ง
if (typeof file === "undefined") {
alert("ํ์ผ์ด ์์ต๋๋ค.");
return;
}
// ์ด๋ฏธ์ง ์ฌ์ด์ฆ 5MB ์ดํ๋ก ์ ํ
if (file.size > 5 * 1024 * 1024) {
alert("ํ์ผ ์ฉ๋์ด ๋๋ฌด ํฝ๋๋ค.(์ ํ: 5MB)");
return;
}
// API ํธ์ถ
const result = await uploadFile({ variables: { file } });
setImageUrl(result.data?.uploadFile.url ?? "");
};
์ฌ์ง ์ ํ ์ ํธ์ถ๋๋ ํจ์์์ ๊ฒ์ฆํ๋ ๋ฐฉ๋ฒ์ด ์๋ค.
const onChangeFile = async (
event: ChangeEvent<HTMLInputElement>
): Promise<void> => {
const file = event.target.files?.[0];
~~~ // ๊ธฐํ ๊ฒ์ฆ ๋ก์ง ์๋ต
if (!file.type.includes("jpeg") && !file.type.includes("png")) {
alert("jpeg ๋๋ png ํ์ผ๋ง ์
๋ก๋ ๊ฐ๋ฅํฉ๋๋ค.");
return;
}
~~~ // API ํธ์ถ ๋ก์ง ์๋ต
};
input ํ๊ทธ์ accept
์์ฑ ํ์ฉํ๋ ๋ฐฉ๋ฒ๋ ์๋ค.
<input
style={{ display: "none" }}
onChange={onChangeFile}
type="file"
ref={fileRef}
accept="image/jpeg,image/png"
// ๋์ด์ฐ๊ธฐ ์์ด ์ฝค๋ง(,)๋ฅผ ๊ธฐ์ค์ผ๋ก ์์ฑ
// accept๋ฅผ ์ถ๊ฐํ๋ฉด ์ง์ ๋์ง ์์ ํ์ฅ์๋ ์ ํ ๋ถ๊ฐ
/>
์ด๋ฏธ์ง ๊ฒ์ฆ ๋ก์ง์ ๋ฐ๋ก ์ปดํฌ๋ํธ๋ก ๋ถ๋ฆฌํ๋ฉด import๋ง ํด์ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์๋ค!
์์์ ์ด๋ฏธ์ง ์ฉ๋์ 5MB๋ก ์ ํํ ๋, 5MB๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ๋ํ๋ด์คฌ๋ค.
5 * 1024 * 1024
์ฉ๋์ ์ฝ๋๋ก ์์ฑํ ๋์๋ ์๋ ๊ทธ๋ฆผ์ ์ฐธ๊ณ ํด์ ์์ ์ฝ๋์ ๊ฐ์ด ๋ํ๋ด์ฃผ๋ฉด ๋๋ค!