이번엔 드디어 파일 업로드 최적화를 해보려고한다.
필자는 파일3개를 업로드 할거다!!
const [fileupload, setFileUpload] = useState<File>();
나는 분명 3개의 공간을 만든다고 했다. 그렇지만 1개의 파일만 올릴때는? 이때 비어있는? 남아있는? 2개의 index
는 undefined
을 반환한다.
이때 연산자를 활용해 작성해주면된다. 주로 조건문에서 사용하는 또는 |
연산자를 사용하면 간단하게 해결할수 있다.
const [fileuploads, setFileUploads] = useState<(File | undefined)[]>([
undefined,
undefined,
undefined,
]);
File
이나 undefined
가 들어있는 배열을 만들어주면된다.
다음 할일은?! state를 보면 첫번째, 두번째 ,세번째 index
를 만들어 줬으니 input
태그와 img
태그에 들어가는 함수에도 index
를 넣어줘야한다.
<input type="file" onChange={onChangeFile(0)} />
<input type="file" onChange={onChangeFile(1)} />
<input type="file" onChange={onChangeFile(2)} />
<img src={imageUrls[0]} />
<img src={imageUrls[1]} />
<img src={imageUrls[2]} />
현재 함수는 인자값을 따로 받지 않고 있기 때문에 함수도 변경해줘야 한다.
const onChangeFile = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) {
alert("파일이 없습니다");
return;
}
const onChangeFile =
(number: number) => (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) {
alert("파일이 없습니다");
return;
}
url
3개 받아야하니까! 배열로 만들어주자
const [imageUrl, setImageUrl] = useState("");
const [imageUrl, setImageUrl] = useState(["","",""]);
const onChangeFile = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) {
alert("파일이 없습니다");
return;
}
const fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = (data) => {
if (typeof data.target?.result === "string") {
setImageUrl(data.target?.result);
setFileUpload(file);
}
};
};
const onChangeFile =
(number: number) => (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) {
alert("파일이 없습니다");
return;
}
const fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = (data) => {
if (typeof data.target?.result === "string") {
const tempUrls = [...imageUrls];
tempUrls[number] = data.target?.result;
const tempFiles = [...fileuploads];
tempFiles[number] = file;
setImageUrls(tempUrls);
setFileUploads(tempFiles);
}
};
};
위 내용에 중요하게 보고 넘어가야하는 부분은 얕은 복사를 활용하는거다.
얕은 복사는
원본을 참조하게끔 주소를 가져오는 것이다
const tempUrls = [...imageUrls] //스프레드연산자
const tempUrls = [...imageUrls];
tempUrls[number] = data.target?.result;
tempUrls[number]
에 numbers
는 배열에 index
를 의미한다. 기존 이미지에서 number
에 해당하는 index
만 data.target?.result
으로 바꿔 주면된다.
tempUrls
는 기존 이미지 url들 중에서 3개의 박스가 있는데
선택한index
만 변경한다.
첫번째 index
를 선택했다면 number
에 0이 들어갈꺼고 두번째 index
를 선택하면 1이 들어가고 세번째 index
선택하면 2가 들어간다.
여기서 index
를 구분해줄수 있는건 위 내용중 함수를 보면 알수 있다.
const onChangeFile =
(number: number) => (e: ChangeEvent<HTMLInputElement>)
함수의 인자값을 보면 number
라고 확인되는데 추가된 태그에도 숫자값을 인자로 넣어 구분해줬기 때문에 구분이 가능하다.
<img src={imageUrls[0]} />
<img src={imageUrls[1]} />
<img src={imageUrls[2]} />
이녀석도 같은 맥락이다.
기존 파일을 가져와 현재 index
에 있는거만 파일을 변경 해주면된다.파일도 똑같은 배열이기 때문이다.
const tempFiles = [...fileuploads];
tempFiles[number] = file;
기존에 있는 fileuploads
를 가져와 변경된 index 부분만 변경해주면된다.
<input type="file" onChange={onChangeFile(0)} />
<input type="file" onChange={onChangeFile(1)} />
<input type="file" onChange={onChangeFile(2)} />
여기 까지가 state에 저장되는 과정이다.
이제 마지막으로 해줄일은 실제로 백엔드로 요청하는 일만 남았다.
await uploadFile({ variables: { file: fileuploads[0] } });
await uploadFile({ variables: { file: fileuploads[1] } });
await uploadFile({ variables: { file: fileuploads[2] } });
이런식으로 3번을 요청해야하는데 실제로 이렇게 요청해도 없다 코드는 상황마다 달라지기 때문에 정답은 없다.
하지만 좋은 코드는 존재한다! 저거보단 좋은 코드가 맞을거라고 생각한다..
필자는 map
과 Promise.all
를 사용하려 한다.
Promise.all(
[](x)
)
Promise.all
안으로 map
을 사용하기 때문에 굳이 []
작성하지 않아도 된다.
map
을 사용하기 때문에 []
을 사용하지 않는것이지 Promis.all
은 무조건 배열이 들어가 있어야 한다. Promise
들을 기다려 줘야 하기 때문이다.
이전 포스팅에 배열로 url를 담아줬었다. 잠깐 작성했던 코드를 보자.🕵🏻♂️
const result = await Promise.all(
["https://dog1.jpg", "https://dog2.jpg", "https://dog3.jpg"].map((el) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(el);
}, 3000);
});
})
);
url3개를 배열로 만들었지만 이번엔 map
을 사용하여 코드를 만들어보자✏️
방법은 3개정도?? 다른 사람들은 나보다 더 있을수도 있다.
fileuploads.map((el) => {
if (el) {
return uploadFile({ variables: { file: el } });
} else {
return undefined;
}
});
el
은 실제로 업로드 하는 파일이다.
업로드 하는 파일이 있을수도 있고 없을수도 있다 무조건 파일3개를 채워서 업로드 하는건 아니다. 그러므로 파일이 있을때 조건문이 실행된다.
Promise.all은 Promise가 들어가야하는데 undefined이 있으면 실제로 무시한다.
return el ? uploadFile({ variables: { file: el } }) : undefined;
결국 el이 존재하면 실행하고 없으면 undefined이기 때문에 3항연산자를 사용해주면 된다.
어차피 첫시작은 undifined라서&&
사용할수도 있다.
return el && uploadFile({ variables: { file: el } });
const results = await Promise.all(
fileuploads.map((el) => el && uploadFile({
variables: { file: el } }))
);
results
안에는 각각의 데이터들이 배열로 들어가 있다.
이제는 여기에 url
만 뽑아서 백엔드로 요청해야한다.
const resultUrls = results.map((el) =>
el?.data ? el?.data?.uploadFile.url : ""
);
url
이 없을수도 있다. url
이 있으면 return
해주고 없을때는 빈문자열을 넣어주면된다.
const onClickSubmit = async () => {
try {
const results = await Promise.all(
fileuploads.map((el) => el && uploadFile({
variables: { file: el } }))
);
const resultUrls = results.map((el) =>
el?.data ? el?.data?.uploadFile.url : ""
);
const result = await createBoard({
variables: {
createBoardInput: {
writer: "Chan",
title: "image",
contents: "이미지 업로드",
images: resultUrls,
},
},
});
console.log(result.data?.createBoard._id);
alert("생성완료");
} catch (e: any) {
Modal.error({ content: e.message });
}
};
이미지 최적화 하는 방법은...실제로 프로젝트 하면서 사용했던 코드이다. 누군가에게 도움이 되기를 바라면서 포스팅 했지만 이글을 보고 이해할수 있다면 당신은... 개발하기 위해 태어난 사람이라고 생각한다..
복습하면서 당시 배웠던 내용을 필기 해놔서 다행이긴 한데 그래도 기억이 가물가물하다.. 하하 완전히 내머릿속에서 지워지기 전에 남겨본다..