17Day

김하은·2023년 2월 6일
0
post-custom-banner

코드리펙토링...

이미지 업로드 +cs(컴퓨터 사이언스)

(실무에서 알아둬야하는 CS)


이미지 선택 후 등록 --> 벡엔드 --> 데이터 베이스x
사진같은 용량이 큰 파일들을 저장해두는 컴퓨터가 존재.
이런것을 제공하는회사 = 클라우드!!
클라우드
비용만 지불한다면 파일얼마든지 저장가능.(AWS,GCP,Azure) ==>
클라우드 프로바이더 라고함.

왜 클라우드인가?
어디서든 접속해 사용이 가능하기에

저장하는곳 = 스토리지
벡엔드는 파일을 받으면 스토리지에 저장!
클라우드는 해당 파일을 저장하고, 다운받을 수 있는 주소를 벡엔드로 -> 벡엔드에서 브라우저로

등록하기 -> mutation날라감 -> 이미지는 클라우드에 저장 -> 이미지 다운로드 주소 받아옴 --> 이 주소가 문자열 형태로 DB에 저장이 되며 브라우저에서도 이 주소를 받아옴.

용량의 문제로 클라우드에 저장하고 다운로드 주소 받아오는 방식을 체택함.


게시글 상세보기 (fetchBoard)-> 제목, 내용 , 이미지 -> DB에서 받아옴.
->> 제목, 내용등 받아옴. , img주소도 받아옴

-> 제목, 내용등 div태그 등에 있는 것은 바로 그려짐.
img태그의 주소 -> 스토리지 해당 주소로 가서 받아오고, 그 다음에야 실제 사진이 들어옴.
=> 사진같은경우 2차적요청이 들어가야하기에 좀느림.

결론: 사진, 동영상등 용량이 큰 것은 내 노트북, DB에 저장하지 않고 따로 클라우드에 저장한다.

브라우저에서 사진을 입력하는 창을 열고 선택하여 등록 -> 벡엔드로 -> 벡엔드에서는 클라우드에 저장하고 주소를 받아 넘겨줌 -> 벡엔드에서 주소를 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",


파일 선택 아이콘 디자인을 바꿀수는 없을까?

=> 기본 아이콘말고 다른 아이콘을 만들어 해당 아이콘을 클릭하면 원래의 파일선택 아이콘이 선택되게
=>>> 태그를 변수에 저장한다 라고 일단 보자

useRef

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>
</>
)
post-custom-banner

0개의 댓글