본문 바로가기
프로젝트 답지

[Dap JI ] expo 이미지 업로드

by 띠리에이터 2024. 10. 24.
reat-native 환경에서 이미지 업로드 적용하는 방법, 

 

next..js에선 input의 type ='file'을 사용해서 이미지 업로드를 구현할 수 있지만 react-native에선 없기 때문에 라이브러리를 사용.

 

1. expo-image-picker

2. react-native-picker

 

이 두개가 대표적인듯. 

 

나는 expo를 사용중이기 때문에 expo-image-picker  적용, 

 

last publish도 2일전이니 꾸준하게 디벨롭중인 것 같다.

 

- expo-image-picker 장점

   1. 간편한 설치 및 사용 

      - expo에서 바로 사용 가능하며 네이티브 코드를 건드릴 필요가 없다. 

   2. 안전성

      - expo 환경에 테스트 가능, 다양한 기기에서 일관되게 작용

   3. 이미지 및 비디오 선택 가능

      - 사용자가 이미지를 선택하거나 , 카메라를 사용할 수 있는 기능을 제

   4. 간편한 권한 관리 

      -  카메라 및 미디어 라이브러리 권한을 쉽게 요청

 


 

설치

npx expo install expo-image-picker

 

찾아보니 npm 뿐만 아니라 expo 사이트에서도 예제가 잘 나온다. https://docs.expo.dev/versions/latest/sdk/imagepicker/

 

ImagePicker

A library that provides access to the system's UI for selecting images and videos from the phone's library or taking a photo with the camera.

docs.expo.dev

 

- 예제 코드 분석 -

 

const pickImage = async () => {
    // No permissions request is necessary for launching the image library
    let result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.All,
      allowsEditing: true,
      aspect: [4, 3],
      quality: 1,
    });

    console.log(result);

    if (!result.canceled) {
      setImage(result.assets[0].uri);
    }
  };

 

pickImage 함수를 비동기 (async)로 해서 expo-image-picker 라이브러리를 사용해 이미지 또는 비디오를 선택하고 선택된 이미지의 url을 상태에 저장하는 코드,

 

ImagePicker.launchImageLibraryAsync()  

- 사용자가 이미지/ 비디오 라이브러리에 파일을 선택할 수 있게 해준다. 

 

launchImageLibraryAsync의 옵션 객체들

 

- mediaTypes : 미디어 타입을 선택하는 옵션, all, videos, image가 있다. 

 

- allowsEditing  : 사용자가 이미지를 선택한 후에 편집을 할 수 있는 옵션, boolean값, 

 

- aspect : 이미지 편집시 적용되는 가로 세로 비율, 숫자 배열이 들어간다. 예제는 aspect[4,3] 이므로 가로 세로 4:3 비율이 된다. allowsEditing=true 일때만 적용된다. 

 

- quality : 품질을 결정하는 옵션으로 1이 최고품질을의미한다. 

 if (!result.canceled) {
      setImage(result.assets[0].uri);
    }

- 사용자가 결과를 취소하지 않았을 경우 setImage에 선택된 url을 저장. 

 

result. assets의 옵션들

export type ImagePickerAsset = {

  uri: string;
 
  assetId?: string | null;
  
  width: number;
     
  height: number;
 
  type?: 'image' | 'video';
 
  fileName?: string | null;
  
  fileSize?: number;
  
  exif?: Record<string, any> | null;
  
  base64?: string | null;
 
  duration?: number | null;
  
  mimeType?: string;
};

 

 


 

native에서는 파일을 주로 두가지 방법으로 처리한다.

 

1. url 방식

2. base64  방식

 

 

- URI 기반 파일 처리:

  • React Native에서 파일을 다룰 때는 주로 파일의 경로(URI)를 이용. 예를 들어, 카메라나 미디어 라이브러리에서 선택한 이미지 파일은 경로(URI)로 반환된다.
  • 이 URI를 그대로 사용해 서버에 업로드할 수는 없으며, 네이티브 코드나 라이브러리를 사용해 파일을 읽고, FormData를 통해 전송해야 한다. 

 

- Base64 인코딩:

  • 일부 API나 서버는 파일을 Base64로 인코딩한 후 전송하는 방식을 요구한다. 
  • expo-file-system과 같은 라이브러리를 사용하여 파일을 Base64로 인코딩한 후 이를 서버로 전송할 수 있다. 

 

언제 URL을 사용하고 언제 Base64를 사용할까?

-URL
   - 파일이 서버에 저장되어 있고, 클라이언트가 그 파일에 접근해야 할 때 사용.

   - 이미지, 비디오, PDF 등 대용량 파일을 전송할 때 URL을 사용하는 것이 좋다.

   - 이는 서버가 해당 파일을 호스팅하고 클라이언트는 해당 파일을 URL을 통해 다운로드하는 방식

 

- Base64

   - 작은 파일이나 임베디드 데이터(이미지, 아이콘 등)를 전송하거나 저장할 때 유용

   - 예를 들어, HTML 페이지 안에 이미지를 직접 넣어야 하거나, 네트워크 연결 없이 데이터를 전달해야 할 때 사용

   - 그러나 대용량 파일에 대해서는 Base64가 파일 크기를 늘리기 때문에 적합하지 않다.

 

나는 s3에서 url을 받는 방식으로 이미지를 업로드 하기 때문에 url방식으로 선택했다. 

이 경우ㄴ 서버에서 이미지를 저장하고, 해당 이미지의 URL만 클라이언트에 전달하는 방식

  const { mutate: imageUpload, isPending } = useMutation({
    mutationKey: ["profileImageUploadKey"],
    mutationFn: async (image: FormData) => {
      const res = await instance.post(`/api`, image);
      return res.data;
    },
    onSuccess: (data) => {
      setFileUrl(data.imageUrl);
    },
    onError: (error) => {
      console.error("파일 업로드 실패:", error);
    },
  });

 

useMutation을 사용해 이미지를 서버에 업로드후 url을 받아오는 비동기 요청을 구현한 코드이다.  

image를 인자로 받아 request body값에 넣어준다. 

 

그 후 업로드가 성공하면 s3에서 리턴하는 url을 setFileUrl에 담아준다. 

 

const pickImage = async () => {
    const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
    if (status !== "granted") {
      alert("카메라 롤 접근 권한이 필요합니다.");
      return;
    }

    let result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images, // 이미지 파일만 선택
      allowsEditing: true,
      aspect: [4, 3],
      quality: 1,
    });


  };

 

사진첩 접근 권한 설정과, 권한이 있으면 이미지에 대한 옵션 설정. 

 

 if (!result.canceled && result.assets.length > 0) {
      const selectedImageUri = result.assets[0].uri;
      const fileExtension = selectedImageUri.split(".").pop()?.toLowerCase();

      // MIME 타입 설정
      const mimeType = fileExtension === "png" ? "image/png" : "image/jpeg";
      const uniqueFileName = `profile-${Date.now()}.${fileExtension}`;

      // FormData에 이미지 추가
      const formData = new FormData();
      formData.append("image", {
        uri: selectedImageUri,
        name: `profile.${fileExtension}`,
        type: mimeType,
      } as any); // TS 오류를 피하기 위한 캐스팅

      // 이미지 업로드 요청
      imageUpload(formData);
    }

 

- selectedImageUri에 사진첩에서 선택한 이미지의 uri를 담아준다. 

 

- fileExtension 함수는 파일 uri를 확인하고 . 을 기준으로 파일의 확장자를 추출한다. => 파일 업로드 확장자 제한하기 위한 것. 

예를 들어 selectedFileUri 가 "file:///path/to/image.jpg"  이런 형식으로 나왔다면 split메서드를 통해

 ["file:///path/to/image", "jpg"]로 반환, 그 후 .pop으로 배열의 마지막 인덱스를 반환해주면 확장자를 소문자로 추출,   

그럼 .jpg만 남는다.

 

- 서버에 폼데이터를 전송하기 위해 new formData 객체 생성, 

- 이미지를 업로드할 때는 파일의 URI, 이름, 그리고 MIME 타입을 지정

   => uri에는 사진첩에서 선택한 이미지의 uri를 , 

   => name은 업로드 시간과 확장자를 기준으로 uniqueFileName 변수를 설정해서 정해준다. 

   => type은 mineType으로 확장자를 추출했을때 png면 image/png 아니면 image/jpeg로 넣어준다.

 

 - 앱에서도 서버로 통신할때 HTTP프로토콜을 사용하기 때문에 mineType 을 지정해줘야한다. 

- mineType 은 서버와 클라이언트가 파일의 **포맷(형식)**을 인식할 수 있도록 정의한 문자열
- 예를 들어, image/jpeg, image/png, text/plain 등

 

- 마지막엔 useMutation 함수인 imageUpload 에 formData객체를 인자로 넣어서 서버 업로드 요청,