코드리펙토링...
(실무에서 알아둬야하는 CS)
이미지 선택 후 등록 --> 벡엔드 --> 데이터 베이스x
사진같은 용량이 큰 파일들을 저장해두는 컴퓨터가 존재.
이런것을 제공하는회사 = 클라우드!!
클라우드
비용만 지불한다면 파일얼마든지 저장가능.(AWS,GCP,Azure) ==>
클라우드 프로바이더 라고함.
왜 클라우드인가?
어디서든 접속해 사용이 가능하기에
저장하는곳 = 스토리지
벡엔드는 파일을 받으면 스토리지에 저장!
클라우드는 해당 파일을 저장하고, 다운받을 수 있는 주소를 벡엔드로 -> 벡엔드에서 브라우저로
등록하기 -> mutation날라감 -> 이미지는 클라우드에 저장 -> 이미지 다운로드 주소 받아옴 --> 이 주소가 문자열 형태로 DB에 저장이 되며 브라우저에서도 이 주소를 받아옴.
용량의 문제로 클라우드에 저장하고 다운로드 주소 받아오는 방식을 체택함.
게시글 상세보기 (fetchBoard)-> 제목, 내용 , 이미지 -> DB에서 받아옴.
->> 제목, 내용등 받아옴. , img주소도 받아옴
-> 제목, 내용등 div태그 등에 있는 것은 바로 그려짐.
img태그의 주소 -> 스토리지 해당 주소로 가서 받아오고, 그 다음에야 실제 사진이 들어옴.
=> 사진같은경우 2차적요청이 들어가야하기에 좀느림.
브라우저에서 사진을 입력하는 창을 열고 선택하여 등록 -> 벡엔드로 -> 벡엔드에서는 클라우드에 저장하고 주소를 받아 넘겨줌 -> 벡엔드에서 주소를 DB로 저장
받아올 때 해당 주소를 받아와 해당 주소로 가서 사진을 받아오면 화면에 사진이 나옴.
사진이 벡엔드에서 클라우드로 왔다갔다 할 수 있는이유
이렇게 크게 확대했을시 각 작은 네모가 rgb색상이 들어가고, 이것이 다 숫자로(문자열로) 변경되어 저장되기에 용량이 크다.
uploadFile뮤테이션 실습전, 사진을 업로드하기 위해서는 아폴로 추가 세팅이 필요하다
(추가 설치하기)
yarn add apollo-upload-client
yarn add --dev @types/apollo-upload-client
import {createUploadLink} from 'apollo-upload-client'
"@apollo/client": "^3.7.1",
파일 선택 아이콘 디자인을 바꿀수는 없을까?
=> 기본 아이콘말고 다른 아이콘을 만들어 해당 아이콘을 클릭하면 원래의 파일선택 아이콘이 선택되게
=>>> 태그를 변수에 저장한다 라고 일단 보자
ref = 참조한다 라는의미
기존 파일선택 태그는 display:none으로 안보이게
그리고 다른 태그를 클릭시 숨겨진 바로 그게 클릭되게
값을 저장하는 것은 useState
태그를 저장하는것은 useRef
저장할 태그에 ref를 적어줌.
const ref이름 = useRef<HTMLInputElement>(null)
클릭하면 ref로 연결한 부분이 실제로 실행되게..
<div style={{ width: "50px", height: "50px", backgroundColor: "gray" }} onClick={onClickImage} 이미지 선택 </div> <input style={{ display: "none" }} type="file" onChange={onChangeFile} ref={fileRef} /> const onClickImage = (): void => { // document.getElementById("파일태그 아이디")?.click(); fileRef.current.click(); };
이미지 검증하기.
: 벡엔드 컴 부하 줄이기
바이트 1024 -> 키로바이트
키로바이트 1024 ->
const file = event.target.files?.[0]; // 배열로 들어오는 이유 :<input type="file" multiple/>일때 여러개 드래그 가능 console.log(file); // 선택한 파일을 콘솔로 찍어본다 // 보내기 전에 검증 if (typeof file === "undefined") { alert("파일이 없다"); return; // 거짓이면 즉시 종료하는 얼리엑시페턴 } if (file.size > 5 * 1024 * 1024) { alert("파일용량이 너무 커요(제한 5MB)"); } ** if (!file.type.includes("jpeg") && file.type.includes("png")) { alert("파일확장자를 확인해주세요.(jpeg, png)"); return; }**
아예 해당 확장자가 아니면 선택 불가하게 막기
input태그 안에
** accept="image.jpeg,imag/png" **
이렇게하면 해당 확장자가 아니면 선택 못하게 막힌다.
두가지 방법 다 유효하다.
해당 검증부분은 공통으로 상품 등록할때도 사용이 가능하니 따로 파일을 빼보자
그런데 기존 return 부분을 자세히 봐야한다.
return이란 해당 함수의 종료를 의미하며 그 아래로 실행을 막는것인데,
따로 파일을 빼고 함수안에 넣었다면 그것은 그 함수에서의 return이기에 그것을 import해서 쓰는 곳에서는 적용되지 않는다 .
따라서 return을 true /false로 시켜준뒤, 해당 함수 실행부분을 변수에 담아 사용한다.
**검증컴포넌트**
>
export const checkValidationFile = (file?: File): boolean => {
if (typeof file === "undefined") {
alert("파일이 없다");
return true; // 그런데 이 함수 안에서 하면 이 함수만 종료됨...
// 따라서 검증을 true false로 해서 함수 호출하는 부분에서
// 거짓이면 즉시 종료하는 얼리엑시페턴
}
if (file.size > 5 * 1024 * 1024) {
alert("파일용량이 너무 커요(제한 5MB)");
return false;
}
if (!file.type.includes("jpeg") && file.type.includes("png")) {
alert("파일확장자를 확인해주세요.(jpeg, png)");
return false;
}
return true;
};
원래있던자리
// 보내기 전에 검증 const isValid = checkValidationFile(file); if (!isValid) return; // file이 없을때. , // 즉, 검증을 통과하지 못했을때는 false가 됨. // , true가 아닐때(false일때) return 시킴 // ========================
...
게시판과 연동하기
먼저 upload뮤테이션을 보내며 해당 이미지가 들어가는 스토리지주소를 받아 state에 저장.
해당 state를 게시글 등록시에 같이 넣어줌(create뮤테이션 보낼때)
const [imgUrl, setImgUrl] = useState(""); const fileRef = useRef<HTMLInputElement>(null); 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/>일때 여러개 드래그 가능 console.log(file); // 선택한 파일을 콘솔로 찍어본다 // 보내기 전에 검증 const isValid = checkValidationFile(file); if (!isValid) return; // file이 없을때. , // 즉, 검증을 통과하지 못했을때는 false가 됨. // , true가 아닐때(false일때) return 시킴 // ========================
const result = await uploadFile({
variables: {
file,
},
});
console.log(result.data?.uploadFile.url); // 스토리지로 들어간 해당 이미지 다운로드 주소 확인 //
// 여기에 구글클라우드 주소를..
setImgUrl(result.data?.uploadFile.url ?? ""); // 스토리지의 해당 사진주소없으면 빈문자열이라도
};
const onClickImage = (): void => {
// document.getElementById("파일태그 아이디")?.click();
fileRef.current?.click();
// ref로 연결된것이 클릭되게
};
const [나의함수] = useMutation(나의그래프큐엘세팅);
const [writer, setWriter] = useState("");
const [title, setTitle] = useState("");
const [contents, setContents] = useState("");
const onChangeWriter = (event: ChangeEvent): void => {
setWriter(event.target.value);
};
const onChangeTitle = (event: ChangeEvent): void => {
setTitle(event.target.value);
};
const onChangeContents = (event: ChangeEvent): void => {
setContents(event.target.value); // input에 입력한 값(발생한 이벤트를)을 state에 저장.
};
const onClickSubmit = async (): Promise => {
// const result = 나의함수();
const result = await 나의함수({
// axios.get 부분과 나의함수() 둘다 같음. 둘다 실행하는것. axios의 경우 get부분이 실행.
// 나의함수()가 실행되면 useMutation의 뒤에 들어있는 부분이 실행됨.
variables: {
createBoardInput: {
// $ === variables variables
애가 $
역할을 함.
writer,
title,
contents,
password: "12",
images: [imgUrl], // 이미지 주소를 넣음. 현재 imgUrl이라는 state에 들어있음
},
},
// : { // // $ === variables `variables`애가 ``역할을 함.
// writer: writer,
// title: title,
// contents: contents,
// },
}); // 함수 실행시에 변수를 전달해줌
console.log(result);
};
return (
<>
작성자:
제목:
내용:
<div
style={{ width: "50px", height: "50px", backgroundColor: "gray" }}
onClick={onClickImage}
>
이미지 선택
</div>
<input
style={{ display: "none" }}
type="file"
onChange={onChangeFile}
ref={fileRef}
accept="image.jpeg,imag/png"
/>
<img src={`https://storage.googleapis.com/${imgUrl}`} alt="" />
{/* 스토리지의 해당 주소의 이미지를 불러옴 */}
<button onClick={onClickSubmit}>등록하기</button>
</>
)