앱 서비스에서 많이 사용되는 기능 중 하나는 바로 이미지 업로드입니다. 우리가 매일 사용하는 SNS나 메신저 서비스에서도 필수적인 기능 중 하나이죠. 오늘은 리액트 네이티브 (그 중에서 Expo)에서 이미지 업로드 기능을 사용하기 위한 방법에 대해 알아보겠습니다!
이미지 업로드라고 하면, 핸드폰의 자체 기능을 사용하는 것이기에 어려울 것 같다는 느낌이 먼저 듭니다. 그러나 많이 사용하는 기능인 만큼, 패키지화가 잘 되어있었는데요. expo를 사용하시는 분들이라면 expo-image-picker를 설치하면 쉽게 사용하실 수 있습니다.
npm install expo-image-picker
그리고 클릭에 사용될 빈 함수를 만들어 두겠습니다.
import React from 'react';
import * as ImagePicker from 'expo-image-picker';
import { Pressable, Text } from 'react-native';
const ImagePickerComponent = () => {
const uploadImage = () => {
// 여기에 코드를 작성할 예정입니다.
};
return (
<Pressable onPress={uploadImage}>
<Text>
이미지 업로드하기
</Text>
</Pressable>
);
};
export default ImagePickerComponent;
빨리 기능을 만들고 싶은 마음에 이미지 업로드 코드를 우선 작성하는 사람이 많은데요. 이미지를 올리기 위해서는 먼저 라이브러리에 접근하기 위한 권한이 필요합니다. 권한 없이 기능만 먼저 만들어 두었다가는 동작하지 않았을 때, 정확한 원인을 찾기가 어려워질 수 있죠.
그래도 다행히 ImagePicker 패키지에는 권한을 확인하고 요청하기 위한 기능도 존재합니다.
import React from 'react';
import * as ImagePicker from 'expo-image-picker';
import { Pressable, Text } from 'react-native';
const ImagePickerComponent = () => {
// 권한 요청을 위한 hooks
const [status, requestPermission] = ImagePicker.useMediaLibraryPermissions();
const uploadImage = async () => {
// 권한 확인 코드: 권한 없으면 물어보고, 승인하지 않으면 함수 종료
if (!status?.granted) {
const permission = await requestPermission();
if (!permission.granted) {
return null;
}
}
// 이미지 업로드 기능 추가 예정
};
return (
<Pressable onPress={uploadImage}>
<Text>
이미지 업로드하기
</Text>
</Pressable>
);
}
export default ImagePickerComponent;
ImagePicker의 useMediaLibraryPermissions hooks를 사용하면 현재 권한 승인을 받은 상태인지 확인하고, 권한을 요청할 수 있는데요. 이미지를 업로드할 때 우선 권한을 확인해주면 됩니다!
이제 본격적으로 이미지 업로드 기능을 추가하면 됩니다!
이미지가 잘 업로드 되었는지 확인하기 위해 imageUrl 상태값을 추가하고, Image 컴포넌트를 만들어두겠습니다.
import React, { useState } from 'react';
import * as ImagePicker from 'expo-image-picker';
import { Pressable, Text } from 'react-native';
const ImagePickerComponent = () => {
// 현재 이미지 주소
const [imageUrl, setImageUrl] = useState('');
// 권한 요청을 위한 hooks
const [status, requestPermission] = ImagePicker.useMediaLibraryPermissions();
const uploadImage = async () => {
// 권한 확인 코드: 권한 없으면 물어보고, 승인하지 않으면 함수 종료
if (!status?.granted) {
const permission = await requestPermission();
if (!permission.granted) {
return null;
}
}
// 이미지 업로드 기능
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: false,
quality: 1,
aspect: [1, 1]
});
if (result.cancelled) {
return null; // 이미지 업로드 취소한 경우
}
// 이미지 업로드 결과 및 이미지 경로 업데이트
console.log(result);
setImageUrl(result.uri);
};
return (
<Pressable onPress={uploadImage}>
<Text>
이미지 업로드하기
</Text>
<Image
source={{ uri: imageUrl }}
/>
</Pressable>
);
}
export default ImagePickerComponent;
이제 코드를 시행해보면, 갤러리에서 이미지 선택 후에 화면의 이미지가 변경되는 것을 확인할 수 있을 것입니다.
그대로 사용하셔도 되지만, 궁금하실 분들을 위해 이미지 업로드 함수의 옵션에 대해서 간단히 설명드리자면 다음과 같습니다.
- mediaTypes: 어떤 타입의 파일을 업로드할지 설정이 가능합니다. All로 둘 수도 있는데, 저는 이미지만 받기 위해 Images로 설정했습니다.
- allowsEditing: 이미지 업로드 전에 자르기 등의 추가 편집 가능 여부를 설정할 수 있습니다.
- quality: 이미지 압축 여부입니다. 1로 설정하면 가장 높은 품질로 파일을 업로드합니다.
- aspect: 이미지 비율을 설정하는 값입니다.
이미지 업로드 기능은 react-native 코드만으로 완성되는 것이 아닙니다.
디비에 실제 이미지를 업로드해야 하기 때문인데요. 만약 서버를 따로 만들었다면, 서버에 이미지 데이터를 포함한 요청을 전송해야 합니다.
(참고자료: NodeJS에서 AWS S3를 이용한 이미지 업로드 기능 만들기)
import React, { useState } from 'react';
import * as ImagePicker from 'expo-image-picker';
import { Pressable, Text } from 'react-native';
const ImagePickerComponent = () => {
// 현재 이미지 주소
const [imageUrl, setImageUrl] = useState('');
// 권한 요청을 위한 hooks
const [status, requestPermission] = ImagePicker.useMediaLibraryPermissions();
const uploadImage = async () => {
// 권한 확인 코드: 권한 없으면 물어보고, 승인하지 않으면 함수 종료
if (!status?.granted) {
const permission = await requestPermission();
if (!permission.granted) {
return null;
}
}
// 이미지 업로드 기능
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: false,
quality: 1,
aspect: [1, 1]
});
if (result.cancelled) {
return null; // 이미지 업로드 취소한 경우
}
// 이미지 업로드 결과 및 이미지 경로 업데이트
console.log(result);
setImageUrl(result.uri);
// 서버에 요청 보내기
const localUri = result.uri;
const filename = localUri.split('/').pop();
const match = /\.(\w+)$/.exec(filename ?? '');
const type = match ? `image/${match[1]}` : `image`;
const formData = new FormData();
formData.append('image', { uri: localUri, name: filename, type });
await axios({
method: 'post',
url: '{API주소}',
headers: {
'content-type': 'multipart/form-data',
},
data: formData
})
};
return (
<Pressable onPress={uploadImage}>
<Text>
이미지 업로드하기
</Text>
<Image
source={{ uri: imageUrl }}
/>
</Pressable>
);
}
export default ImagePickerComponent;
이제까지 대부분의 api 요청은 json 형식의 요청이었을 것입니다. 그러나 이미지와 같은 멀티파트 폼데이터는 FormData를 만들어서 전송해야 합니다. 이 때 headers에 content-type: 'multipart/form-data'를 설정하고, data에 formData를 그대로 넣어야 한다는 점을 유의해 주세요.
- 꼭 axios 가 아니어도 됩니다. 기본적으로 내장된 fetch나, 다른 라이브러리를 사용해도 무관합니다.
- formData.append의 첫 파라미터는 key 값으로, 서버와 합의된 문자열을 자유롭게 사용하시면 됩니다.
이제 코드를 실행해보면, 실제 서버에 요청이 잘 전송되는 것을 확인하실 수 있을 것입니다!
만약 서버 요청에 다음과 같은 에러가 발생하시나요?
TypeError [ERR_INVALID_ARG_TYPE]: The "key" argument must be of type string or an instance of ArrayBuffer, Buffer, TypedArray, DataView, KeyObject, or CryptoKey.
이는 아마도 formData.append에 설정한 key값이 서버에서 받는 키값과 달라서 발생하는 오류일 것입니다. 서버와 키 값을 비교해보고 서로 같도록 통일하면 금방 해결될 것입니다!
(서버 키 값이 어떤 것을 말하는지 헷갈린다면, 상단의 NodeJS에서 이미지 업로드하기 아티클을 읽어주세요)
만약 타입스크립트를 사용중이라면 FormData의 append를 하는 과정에서 아래 그림과 같이 에러가 발생할 수 있습니다.
기본으로 설정된 FormData와 타입이 다르기 때문인데, 이런 경우 form-data 패키지를 설치하면 바로 해결할 수 있습니다.
npm install form-data
그리고 파일 상단에 아래와 같이 FormData를 import 해주면 문제 해결!
import FormData from 'form-data'
설명을 위해서 코드를 한 파일에 작성했는데요. 서버 요청과 이미지 select 코드를 분리하면 더 깔끔하게 관리할 수 있을 것입니다.
새로운 지식을 기반으로 더 멋진 코드를 작성할 수 있기를 바라며, 오늘도 해피코딩하세요 :)
position fixed 깨지는 이슈! fixed 사용 전 필수 상식 (0) | 2022.02.01 |
---|---|
react-query 캐싱 이슈 해결하기! 변경, 삭제할 때 쿼리 업데이트가 안된다? (0) | 2022.01.31 |
NodeJS 이미지 업로드 기능 만들기 (AWS S3 활용하기) (0) | 2022.01.30 |
REST API 컨벤션 Top5! 단수, 복수, 네이밍 등의 url 설계를 위한 best practice 알아보기 (0) | 2022.01.13 |
Express에서 JWT 토큰 인증 미들웨어 만들기 (Typescript 적용까지) (1) | 2021.12.22 |
댓글 영역