본문 바로가기
Study/Come_On_Web

RTKQuery로 전역으로 이미지 저장하기

by 예 강 2023. 6. 16.

전역 상태로 이미지를 전역상태로 저장해야 하는 경우가 생겼습니다!
자 다같이 함께 해결해봅시다!🔥

상황설명

사용자가 업로드한 이미지를 다른 페이지에 전달해야 하는 일이 생겼습니다.
보통의 경우엔 API로 전송하고 페이지 이동을 했겠지만,
아차차! 저희 프로젝트는 데이터를 원기옥처럼 모아서 마지막에 저장하는 로직을 선택했습니다.

그래서 우리는 전역으로 이미지 데이터를 전역으로 저장한 후, 다른 페이지에서 불러와서 써야합니다. 자 가볼까요 🔥( •̀ ω •́ )🔥(휘리릭)

두가지 방법에 대해 알아보도록 하겠습니다!
1. file=> objectUrl =>file
2. file => base64 => file

blob객체가 뭘까요?
blob은 (Binary Large Object)의 약자로 바이너리 형태로 용량이 큰 객체를 저장할 때 사용하는 자료형입니다. 보통 멀티미디어 자료인 이미지, 비디오 , 사운드 같은 자료를 저장할때 사용합니다!


대부분 이런형태로 이미지 파일을 사용자로부터 받을 겁니다. onChangeImg 이벤트가 발생하면 핸들러에서 e.target을 통해 이미지 파일을 전달받습니다.

하. 지. 만! 우리의 이미지 파일을 RTGQ에 저장하기 위해선 object가 아닌 String 형태로 넣어줘야 합니다. RTKQ에선 파일데이터를 전역으로 저장할 수 없기 때문이죠

저희가 쓸 방법은 두가지입니다.

  1. 파일 -> blob객체 =>파일
  
  const onChangeImage = (e: React.ChangeEvent<HTMLInputElement>): void => {
    if (e.target.files) {					//image Input창에서 파일을 받아옵니다.
      const fileUrl = URL.createObjectURL(e.target.files[0])
      const img = fileUrl					//blob객체로 바뀐 파일
      setImage(String(img))					//저장을 위해 string으로 바꿔줍니다.
    }

핵심은 URL.createObjectUrl() 입니다

해당 메서드는 주어진 객체를 가리키는 url을 DOMString으로 반환하는데, 자신을 생성한 창의 document가 사라지면 무효화 됩니다.

즉 document 내부에서 쓸 수 있게 주어진 객체에 대한 DomString을 생성한다고 하네요!

같은 객체를 쓰더라도 메서드를 호출할때마다 매번 다른 Url을 가진 새로운 객체를 생성하기 때문에 각각의 Url을 더는 쓰지 않을땐 URL.revokeObjectURL()을 해줘야 한다고 합니다.

하지만 제 경우엔 해당 url을 전역으로 받을 거라 딱 한번 url을 생성하기 때문에 상관없습니다!
만약 저 string문자를 그대로 전달하려고 하면 서버에서 String을 멀티파일로 변환할 수 없다고 할겁니다!
그래서 저희는 formData 형식에 넣을땐 string으로 변환된 objectUrl을 다시 파일로 바꿔줘야 합니다!

//파일 => url
 const changeObjectUrlToFile = async (): Promise<Blob> => {
    const file = await fetch(image || "").then((r) => r.blob())
    return file
  }

스택 오버플로우를 뒤져서 찾아낸 코드입니다. image에 fetch해서 나온 결과를 blob 즉 파일객체로 만들어주고 있습니다.

이렇게 반환된file객체를 폼데이터에 저장합니다!

redux에 string 형식으로 저장된 url

postMan 에 저장된 file 형태

  1. file => base64 => file

    바로 URL.createObjectUrl() 를 안쓰고 하는 방법인데, 바로 base64 인코딩을 이용하는 방법입니다.

Base64 인코딩이란?
이미지 와 같은 BinaryData를 character set에 영향을 받지 않는 공통 ASCII영역의 문자로만 이루어진 문자열로 바꾸는 Encoding입니다. 64는 64진법을 사용하기 때문에 Base64입니다~

  const encodeFileToBase64 = (fileBlob: Blob): Promise<void> => {
    const reader = new FileReader()
    reader.readAsDataURL(fileBlob)
    return new Promise<void>((resolve) => {
      reader.onload = () => {
        if (!reader.result) {
          throw new Error("No img result")
        }
        resolve(setImageSrc(reader.result))
      }
    })
  }

fileReacer를 생성해준뒤 readAsDataUrl()을 사용합니다.
readAsDataUrl()은 blob이나 file객체를 읽어 base64로 인코딩된 스트링 데이터를 반환합니다.

이런식의 무지막지한 string 데이터가 RTKQ에 전역으로 보내줍니다.

자 이걸 다시 file 형태로 만들어 줘야 합니다!

const dataUrlToFile = (dataUrl: string, filename: string): File | undefined => {
  const arr = dataUrl.split(",")
  if (arr.length < 2) {
    return undefined
  }
  const mimeArr = arr[0].match(/:(.*?);/)
  if (!mimeArr || mimeArr.length < 2) {
    return undefined
  }
  const mime = mimeArr[1]
  const buff = Buffer.from(arr[1], "base64")
  return new File([buff], filename, { type: mime })
}

위 코드가 base64를 다시 파일형태로 만들어주는 코드인데요 , 를 기준으로 파일정보와, 이진데이터 부분을 분리한 후,
또 :/;와같은 특수기호를 사용해 파일의 타입을 분리한 코드입니다. ex)image
Buffer.from()을 이용해서 문자열 이미지 데이터를 base64로 인코딩해서 버퍼에 담습니다.

new File 로 파일객체를 만들기 위해선 첫번째 인자로 array, arrayBuffer, Blob, 문자열 등이 들어가야 하는데, Buffer에 담긴 가 들어가야 하기때문에 buffer를 배열에 담아 전해주고, 파일네임과, 위에서 걸러낸 타입을 적어줍니다.

  const makeFormData = async (): Promise<FormData> => {
    const formData = new FormData()
    formData.append("title", courseDetail.title)
    formData.append("description", courseDetail.description)
    const myfile = dataUrlToFile(courseDetail.imgFile, "코스화면.png")

    if (myfile !== undefined) {
      await myfile?.arrayBuffer().then((arrayBuffer) => { //arrayBuffer를 blob객체로 만든다.
          const blob = new Blob([new Uint8Array(arrayBuffer)], {
          type: myfile.type,
        })
      })
      formData.append("imgFile", myfile)
    }

    return formData
  }

file객체를 blob으로 저장하기 위해 arrayBuffer를 unit8array로 변환해서 blob으로 저장합니다! 왜 unit8array를 쓰냐면 8byte의 메모리상에 저장하기 때문이다! (8byte = 64bit)

이렇게 새로만든 파일을 폼데이터에 저장해주면 멀쩡한 파일이 들어갑니당 굿 !

base64 encoding을 하게되면 전송해야할 데이터의 양도 약 33% 늘어나기 때문에 비효율적인것 같아서 처음엔 base64 형식으로 했다가 createObjectUrl 방식을 차용했습니다.

후에 base64는 어떤경우에 사용하는걸까? 찾아보니까.

ASCII는 7 bits Encoding인데 나머지 1bit를 처리하는 방식이 시스템 별로 상이하다.
일부 제어문자 (e.g. Line ending)의 경우 시스템 별로 다른 코드값을 갖는다.

위와 같은 문제로 ASCII는 시스템간 데이터를 전달하기에 안전하지가 않다. Base64는 ASCII 중 제어문자와 일부 특수문자를 제외한 64개의 안전한 출력 문자만 사용한다.
(* 안전한 출력 문자는 문자 코드에 영향을 받지 않는 공통 ASCII를 의미한다).

“Base64는 HTML 또는 Email과 같이 문자를 위한 Media에 Binary Data를 포함해야 될 필요가 있을 때, 포함된 Binary Data가 시스템 독립적으로 동일하게 전송 또는 저장되는걸 보장하기 위해 사용한다” 라고 정리 할 수 있을 것 같다.-https://effectivesquid.tistory.com/entry/Base64-%EC%9D%B8%EC%BD%94%EB%94%A9%EC%9D%B4%EB%9E%80

그렇다고 합니다! 도움이 되었으면 좋겠네요 이상 포스팅을 마치겠습니당 ψ(`∇´)ψ

'Study > Come_On_Web' 카테고리의 다른 글

no patch files found  (0) 2023.06.16
Come_On_Web 리팩토링하기  (0) 2023.06.16