이미지 프로세스를 이해하기 위해서는 storage
사용을 알아야 합니다.
storage
또한 컴퓨터이며 여러 컴퓨터들을 연결시켜 놓은 큰 용량을 담을 수 있는 데이터베이스
입니다.
uploadFile
이라는 api
가 있을때, 파일을 선택하고 uploadFile
을 요청하게 될 경우, backend
에서 storage
로 파일을 전송하게 됩니다.
우리가 이미지를 보기 위해서는 주소를 사용해 접근하게 됩니다.
즉, storage
에서는 backend
로 이미지 주소를 넘겨주게 되며 이 주소를 다시 front에게 주게 됩니다. 우리는 이 주소를 가지고 api 요청 시, image에 대한 주소를 보낼 수 있게 되는 것 이며, 이 정보들을 DataBase
에 저장하게 됩니다.
❗️ 참고로 imgFile에 접근할 수 있는 주소만 저장하며 file은 없습니다
우리가 이미지 업로드에 사용할 api는 uploadFile
입니다.
이미지 업로드는 uploadFile
로 받아온 이미지 url을 createBoard
에 넣어주시면 됩니다.
url을 받아오기 위해선 아폴로 업로드 관련 라이브러리를 설치해줘야 합니다. 아래 순서를 따라 설치와 세팅을 해주시길 바랍니다.
url을 가지고 오기 위한 라이브러리로 createUploadLink
를 설치해야 합니다.
터미널에 yarn add apollo-upload-client
입력해 설치해 주세요.
app.tsx
에서 세팅
// class 폴더의 app.tsx파일
//import 부분
import {ApolloLink} from "@apollo/client"
import {createUploadLink} from "apollo-upload-client"
//세팅 함수 부분
const uplodLink = createUploadLink({
uri: "백엔드 주소"
})
const client = new ApolloClient({
link: ApolloLink.from([uplodLink as inknown as ApolloLink]),
cache: new inMemoryCache(),
})
아폴로 업로드는 타입스크립트 또한 지원해주기 때문에 타입을 설치해 주시기 바랍니다.
yarn add @types/apollo-upload-client --dev
를 설치해줍니다.
파일을 선택할 수 있도록 input
태그의 type에 file로 지정해 그려주겠습니다.
index.tsx
// 이미지 업로드 api 사용을 위한 쿼리 작성
const UPLOAD_FILE = gql`
mutation uploadFile($file: Upload!) {
uploadFile(file: $file){
url
}
}
`
const ImageUPloadPage = () => {
const [uploadFile] = useMutation(UPLOAD_FILE)
// 이미지 업로드 함수
const onchangeFile = async(e: ChangeEvent<HTMLInputElement>) => {
//files는 있을수도 있고,없을수도 있기 때문에 옵셔널 체이닝을 사용해주셔야 합니다.
const Imagefile = e.target.files?.[0]
try {
// 우리가 선택한 사진을 Imagefile라는 변수에 담았으니 해당 변수를 variables에 넣어서 보내주면 됩니다.
await uploadFile({ variables: { file: Imagefile} })
console.log(result.data?.uploadFile.url)
} catch(error){
alert(error.message)
}
}
return <input type="file" onChange={onChangeFile}/>
}
이미지를 보내주고, url을 받아오는 과정 을 자세히 보도록 하겠습니다.
onChange
를 통해 이미지 파일을 가지고와 Imagefile
에 넣어줍니다.Imagefile
을 variables
에 넣어 uploadFile mutation을 날려줍니다.❗️ 여기서 학습한 것은 이미지 업로드하는 과정이나 등록한 것이 아닙니다!
이미지를 업로드할때 필요 이상으로 큰 사이즈의 이미지나, 초고화질 이미지를 보내게 되면 저장공간을 많이 차지하기 때문에 비용측면에 있어서 부담이 될 수 있습니다.
또한 이미지를 업로드하는데 html파일이나 한글파일을 잘못 업로드 하는 경우도 있습니다.
따라서 위와 같은 일들을 방지하기 위해 이미지를 업로드 할 때 이미지의 크기를 지정하고, 확장자를 검증하는 등 이미지 검증 단계를 거쳐 업로드 하는 것이 좋습니다.
이미지를 5MB 이하의 사이즈만 넣을 수 있도록 해보겠습니다.
// 이미지 사이즈 검증
const UPLOAD_FILE = gql`
mutation uploadFile($file: Upload!) {
uploadFile(file: $file) {
url
}
}
`
const ImageUPloadPage = () => {
const onchangeFile = async(e: ChangeEvent<HTMLInputElement>) => {
const [uploadFile] = useMutation(UPLOAD_FILE)
// files는 있을수도 있고,없을수도 있기 때문에 옵셔널 체이닝을 사용해주셔야 합니다
const Imagefile = e.target.files?.[0]
// 이미지 파일의 사이즈가 없으면 경고띄워주기
if(!file?.size) {
alert("파일이 존재하지 않습니다.")
return
}
// 이미지 파일의 사이즈가 있지만, 5MB보다 클경우 경고를 띄우고 함수를 종료합니다.
if(file?.size > 5 * 1024 * 1024) {
alert("파일 용량이 너무 큽니다.(제한: 5MB)")
return
}
try {
await uploadFile({ variables : { file: Imagefil } })
console.log(result.data?.uploadFile.url)
} catch(error) {
alert(error.message)
}
}
return <input type="file" onChange={onChangeFile}/>
}
이미지 확장자가 png, jpeg가 아니면 업로드할 수 없도록 하겠습니다.
// 이미지 사이즈 검증
const UPLOAD_FILE = gql`
mutation uploadFile($file: Upload!) {
uploadFile(file: $file) {
url
}
}
`
const ImageUPloadPage = () => {
const onchangeFile = async(e:ChangeEvent<HTMLInputElement>) => {
const [uploadFile] = useMutation(UPLOAD_FILE)
// files는 있을수도 있고,없을수도 있기 때문에 옵셔널 체이닝을 사용해주셔야 합니다.
const Imagefile = e.target.files?.[0]
// 이미지 파일의 사이즈가 없으면 경고띄워주기
if(!file?.size) {
alert("파일이 존재하지 않습니다.")
return
}
// 이미지 파일의 사이즈가 있지만, 5MB보다 클경우 경고를 띄우고 함수를 종료합니다.
if(file?.size > 5 * 1024 * 1024) {
alert("파일 용량이 너무 큽니다.(제한: 5MB)")
return
}
// 이미지 파일의 확장자 검증
if(!file.type.includes("png") && !file.type.includes("jpeg")) {
alert("jpeg 파일 또는 png 파일만 업로드 가능합니다.")
}
try {
await uploadFile({ variables : { file: Imagefil } })
console.log(result.data?.uploadFile.url)
} catch(error) {
alert(error.message)
}
}
return <input type="file" onChange={onChangeFile}/>
}
🔔 사이즈보는 방법
MB(메가바이트) KB(키로바이트) B(바이트)
1024B = 1KB
1024KB = 1MB
src/commons/utils.ts
export const checkFileValidation = (file?: File) => {
// 이미지 파일의 사이즈가 없으면 경고띄워주기
if(!file?.size) {
alert("파일이 존재하지 않습니다.")
return false;
}
// 이미지 파일의 사이즈가 있지만, 5MB보다 클경우 경고를 띄우고 함수를 종료합니다.
if(file?.size > 5 * 1024 * 1024){
alert("파일 용량이 너무 큽니다.(제한: 5MB)")
return false;
}
// 이미지 파일의 확장자 검증
if(!file.type.includes("png") && !file.type.includes("jpeg")){
alert("jpeg 파일 또는 png 파일만 업로드 가능합니다.")
return false;
}
return true
}
파일을 분리하지 않았을 때는 if문에 return 을 넣어주어 함수를 종료 시켜주었지만, 파일을 분리하게 되면 검증함수를 종료할 뿐 이미지 업로드 함수를 종료하지 못합니다.
따라서 검증함수를 따로 분리했을 때는 true와 false를 return 하여 결과를 변수에 담아 해당 변수의 Boolean값에 따라 return으로 업로드를 막거나 업로드를 진행 해주시면 됩니다.
// 이미지 검증을 실행 할 컴포넌트
import {checkFileValidation} from '파일경로'
const UPLOAD_FILE = gql`
mutation uploadFile($file: Upload!){
uploadFile(file: $file){
url
}
}
`
const ImageUPloadPage = () => {
const onchangeFile = async(e:ChangeEvent<HTMLInputElement>) => {
const [uploadFile] = useMutation(UPLOAD_FILE)
//files는 있을수도 있고,없을수도 있기 때문에 옵셔널 체이닝을 사용해주셔야 합니다.
const Imagefile = e.target.files?.[0]
//이미지 검증 함수를 끌어오겠습니다.
const isValid = checkFileValidation(Imagefile)
//이미지 검증의 결과값에 따라 업로드를 진행하거나 함수를 종료합니다.
if(!isValid) return
try {
await uploadFile({ variables : { file: Imagefil } })
console.log(result.data?.uploadFile.url)
} catch(error) {
alert(error.message)
}
}
return <input type="file" onChange={onChangeFile}/>
}
위의 이미지는 input
태그입니다. 기본은 예쁘지 않으니까 좀 더 예쁘게 꾸며주는 방법을 알아보겠습니다.
Label
태그에는 htmlFor
이라는 속성이 있습니다. 이 속성에 값을 넣으면 값과 똑같은 id를 찾아 그 태그의 기능과 연결해줍니다.
<div>
<label htmlFor="fileTag">이거 눌러도 실행돼요!</label>
<img style={{ width: '500px' }} id="image" />
<input id="fileTag" type="file" onChange={readImage} />
</div>
노란색 부분이 label
태그 입니다. htmlFor="fileTag" 와 input의 id="fileTag" 의 값이 똑같습니다.
그럼 어떤 결과가 있는지 한 번 보겠습니다.
위의 결과를 보면 label
태그를 클릭해도 똑같이 파일을 올릴 수 있습니다.
그러면 label
태그를 우리가 원하는 디자인으로 꾸미고, 기존 input
태그는 안 보이도록 CSS를 작업하면 됩니다.
우리가 HTML 태그를 선택할 때는 보통 getElementId
를 사용했습니다.
react에서는 HTML 태그에 접근을 도와주는 역할을 useRef
가 하고 있습니다.
우선 간단하게 사용 방법을 보도록 하겠습니다. 위의 예제에서 htmlFor
부분을 없애고 useRef
로 똑같이 한 번 만들어 보겠습니다.
import { useRef } from 'react';
export default function Web() {
const inputEl = useRef();
}
우선 바뀐 부분을 먼저 보기 위해 이미지 미리보기 코드는 잠시 지웠습니다.
보시면 제일 위에서 import로 useRef
를 가져오고 const inputEl = useReft();
를 작성했습니다.
이게 가장 처음 Ref를 불러오고 사용하는 기본 설정입니다. useState
나 useEffect
처럼 react에서 가져와야 사용할 수 있습니다.
이렇게 하고 inputEl
을 태그에 넣어주면 그 태그는 inputEl
로 대신 사용할 수 있습니다.
태그에 넣어주는 것도 한 번 코드로 보곘습니다.
<input
ref={inputEl}
id="fileTag"
type="file"
onChange={readImage}
/>
이렇게 input
태그에 ref={inputEl}
을 작성하면 이제 input
태그를 inputEl
을 이용해 사용할 수 있습니다.
그리고 나서, useRef
에 있는 기능을 이용해주면 됩니다.
useRef
에는 다양한 기능이 있지만, 지금은 하나만 보도록하겠습니다.
const handleFileBtn = () => {
inputEl.current.click();
};
함수를 하나 만들어주었습니다.
onClick
에 넣을 함수인데, Ref에는 current
안에 click
이라는 기능이 있습니다.
이름 그대로 current
는 inputEl
에 들어온 태그를 뜻하고, 그 태그를 click
하겠다는 기능입니다.
<button onClick={handleFileBtn}>이미지 등록 버튼</button>
그리고 새로운 버튼을 하나 만들어서 해당 기능을 넣어주었습니다.
이렇게 하면 button
을 클릭했을 때 inputEl.current.click();
이 실행될 것이고, 그것은 우리가 useRef
에 넣어두었던 input
태그를 클릭한 것과 같은 결과가 나올 것입니다.
보시면 이미지등록버튼 을 눌렀을 때 input
태그를 클릭한 것과 같은 결과가 발생합니다.
여기까지 하면 useRef
로 input
태그의 기능을 대신 하는 것은 끝났습니다.
조금 더 응용을 해볼수도 있습니다.
return (
<>
<div>
<img onClick={handleFileBtn} style={{ width: '500px' }} id="image" />
<input
hidden={true}
ref={inputEl}
id="fileTag"
type="file"
onChange={readImage}
/>
<button onClick={handleFileBtn}>이미지 등록 버튼</button>
</div>
</>
);
위의 코드를 보시면 미리보기 이미지에도 handleFileBtn
을 넣어주었습니다. handleFileBtn
이 어떤 기능인지는 위에서 설명을 했습니다.
그리고 input
태그에는 hidden 기능을 이용해 태그를 숨겨주었습니다.
이러면 우리는 미리보기 이미지를 클릭할 때, 이미지 등록 버튼을 클릭할 때 모두 <input type="file"/>
태그를 클릭한 것과 동일한 결과를 볼 수 있습니다.
아래는 전체코드입니다.
import { useRef } from 'react';
export default function Web() {
const inputEl = useRef();
const readImage = (input) => {
// input 태그에 파일이 있는 경우
if (input.target.files && input.target.files[0]) {
// FileReader 인스턴스 생성
const reader = new FileReader();
// reader가 이미지 읽도록 하기
reader.readAsDataURL(input.target.files[0]);
// 이미지가 로드가 된 경우
reader.onload = (e) => {
const previewImage = document.getElementById('image');
previewImage.src = e.target.result;
}
}
};
const handleFileBtn = () => {
inputEl.current.click();
};
return (
<>
<div>
<img onClick={handleFileBtn} style={{ width: '500px' }} id="image" />
<input
hidden={true}
ref={inputEl}
id="fileTag"
type="file"
onChange={readImage}
/>
<button onClick={handleFileBtn}>이미지 등록 버튼</button>
</div>
</>
);
}