프로그래밍 언어/React Native (RN)

multipart/form-data 형태로 JSON 데이터와 이미지 파일 서버에 같이 보내기

chobyeonggyu03 2024. 7. 29. 20:58
반응형

이번 글에서는 3주만에 해결한 React Native 환경에서 mutilpart/form-data 형태로 서버에 JSON데이터와 이미지 파일을 동시에 보내는 방법에 대해 정리해 보도록 하겠다.
 
 
 글쓴이는 여러 시행착오 끝에 react-native-image-picker와 react-native-fs 라이브러리를 활용하여 서버에 JSON 데이터와 이미지 파일을 같이 보내는 걸 성공하였는데, 이 2개의 라이브러리를 활용하는 방법에 대해 설명하기 전에 먼저 서버와 포스트맨을 활용해 어떠한 데이터를 서버에 전달해야 했는지에 대해 정리해 보도록 하겠다. (RN언어가 아니라면 앨범에 접근가능한 사진선택 관련 라이브러리와 파일시스템 라이브러리를 각자 언어에 맞는 라이브러리로 다운받아주면 된다.)
 
 

서버측 코드

 

 
 
 
위의 사진은 백엔드 서버측에서 spring으로 post요청을 처리하는 부분에 대한 메서드이다. 글쓴이도 아직 백엔드에 대한 이해도는 부족하기에 필요한 부분만 정리해 보자면,  @RequestPart와 mutipartFile들을 통해 각각 'dto' 객체와 'photos' 객체의 데이터들을 받아들이고, 받아들인 데이터를 DB에 저장 후 response를 반환하고 있는 모습이다.
 
 
 
 

Postman을 활용한 데이터 전송 형태 테스팅

 
 

 
 
 
 
인증 토큰과 content-type을 multipart/form-data로 지정하여 body에는 'dto'라는 key 값으로  JSON 문자열로 구성된 JOSN 파일과 'photos'라는 key값으로 숙소 관련 이미지를 둘 다 file형태로 보내야 성공적으로 response가 반환되는 모습이다. (포스트맨으로 직접 테스팅해보게 된 건 최근인데 그전에는 JSON 데이터를 file 형태가 아닌 JSON 문자열 형태의 string으로 보내거나, blob으로 감싼 형태로 보내고 있었고, 아래와 같이 "Content-Type 'application/octet-stream' is not supported" 에러를 발생시키고 있었다. )
 
 
 
 

 
 
 
위의 상황처럼 JSON 데이터를 파일이 아닌 문자열로 데이터를 보내게 되었을 때 서버에서 이를 제대로 처리하지 못하는 이유는 @RequestPart의 특징에 있는데 JSON 데이터를 JSON형태의 기본 문자열로 보내게 되면, 해당 데이터의 Content-Type이 text/plain이 되거나, Content-type을 인식하지 못하여 boundary를 제대로 지정하지 못해 application/json이 아닌 'application/octet-stream'으로 해석될 수 있기 때문이다.
 
 
또한 문자열이 아닌 blob의 형태로 시도했었을 때는 포스트맨에서는 blob형태의 데이터는 지원해주지 않아 테스트해보진 못했지만, 웹과 달리 앱환경에서는 blob 기능을 기본적으로 지원해주지 않기에 간접적으로 react-native-blob-util과 같은 라이브러리를 활용해 사용해야 하는데 이 부분에서 제대로 blob처리가 웹과 같은 환경으로 처리되지 않아 제대로 Content-type과 데이터들이 서버로 전달되지 못하여 'application/octet-stream'으로 해석되었던  것 같다. (blob으로 content-type을 명시하여 처리할 수 있지만 서버와 세팅값을 조율하는 것이 필요함, 글쓴이의 상황에서는 blob에 applicaition/json 타입을 설정하여 보내는 걸로는 해결되지 않았었음)
 
 
추가적으로 postman을 이용해 서버에서 클라이언트측의 데이터를 어떻게 처리되도록 로직을 구성했는지 이해하는 과정에서 multipart에 대한 이해도가 있어야 했는데, 서버측에서는 주로 multipart 데이터를 parsing 할 때 @RequestPart 메서드를 사용하는데 해당 메서드는 multipart/form-data의 형태로 보내주지 않으면 데이터를 제대로 parsing 해주지 못한다. 그런데 multipart/form-data는 주로 파일 전송을 포함하는 폼 데이터를 전송할 때 사용되는 Content-Type으로써,  바이너리 데이터와 텍스트 데이터를 함께 전송할 수 있게 해 주는데, boundary라는 데이터 구분자를 통해 한꺼번에 보낸 데이터들을 각 부분이 어떤 데이터를 포함하고 있는지 구분하여 서버에 전달하게 된다.
 
여기서 boundary는 HTTP프로토콜에서 적절한 boundary를 임의로 지정해주게 해야하는데 이때 각 파트에 content-type을 명시해주지 않으면 제대로 파일의 형태를 구분하지 못해 default값으로 content-type을 서버로 전송하게 된다. 즉 클라이언트측에서 multipart/form-data라고 명시하여 데이터를 보내더라도 HTTP통신과정에서 서버에는 'application/octet-stream'과 같은 형태로 데이터가 전송되게 되는 것이다.
 
 (아래 사진의 ---webkitFormBoundary5BBZQ0hH8AMS6SDl 가 구분자역할의 boundary이다. )
 
 
 

 
 
 
 
이렇게 서버가 제대로 동작하는지, 제대로 동작하려면 클라이언트 측에서 어떤 형식을 request를 보내야 하는지에 대해 Postman을 활용해 간단히 테스트해 보았고, 이제 이미지파일과 JSON데이터를 서버에 multipart/form-data형식으로 formData를 활용해 보낼 때 어떻게 해야 하는지에 대해 자세히 다뤄보도록 하겠다.
 
(참고로 파일시스템과 관련된 react-native-fs 라이브러리 사용법에 대해선 이전 글에서 정리해두었기에 image-picker를 사용하는 방법에 대해서만 정리하고 본론으로 넘어가도록 하겠다.)
 
 


1) image-picker와 axios 다운로드 


먼저 image-picker를 사용하기 위해 react-native에서 지원해주는 image-picker 라이브러리를 아래 코드를 입력해 다운로드하여주도록 하자.
 
 

npm install react-native-image-picker

 
 
 
 
 

2) image-picker 권한 설정

 
image-picker 라이브러리를 다운 받아줬다면 [ android - app - src - main ] 경로에 있는 'androidManifest.xml' 파일에 들어가 아래 코드를 입력하여 image-picker에 카메라와 앨범기능에 대한 권한을 설정해 주도록 하자.
 
 

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

 
 
 
 

3) image-picker 라이브러리 import 해주기

 
권한을 설정해주었다면, 이제 해당 라이브러리를 현재 파일에서 사용할 수 있도록 import 해주도록 하자.
 

import { launchImageLibrary } from 'react-native-image-picker';	// image-picker내에 있는 앨범 라이브러리를 사용하기 위해 import

 
 
 
 
 

4) image-picker 사용법

import { launchImageLibrary } from 'react-native-image-picker';		//갤러리에서 이미지나 동영상을 선택하는 라이브러리를 사용하기 위해 import

const selectImage = async () => {					// 이미지를 선택하는 함수
  const options = {							// 선택할 이미지나 영상에 대한 옵션
    mediaType: 'photo',							// 영상인지 이미지인지에 대한 Type 설정
    quality: 1								// 화질을 지정 (1이 최대)
  };

  launchImageLibrary(options, (response) => {				  // 이미지 라이브러리에서 이미지를 선택하는 함수
    if (response.didCancel) {
      console.log('User cancelled image picker');
    } else if (response.error) {
      console.log('ImagePicker Error:', response.error);
    } else {
      const source = { uri: response.assets[0].uri };			// source에 선택한 이미지나 영상파일 저장 속성은 uri, type, name 지정가능
      const uri = source.uri;           		// URI 속성
      const type = source.type;        		 // MIME형식의 type 속성
      const fileName = source.fileName; 		// 파일이름 속성
    }
  });
};

 
imaga-picker 사용법을 아래 예제의 주석을 통해 간단히만 설명하고 본론으로 넘어가 보도록 하겠다.
 
 
 
 
 
주석과 덧붙여 선택한 이미지나 영상을 저장할 때 유용하게 지정할 수 있는 속성들에 대해 간단히 정리해 보겠다.
 
● uri: 파일의 위치를 나타냄
● type: MIME 타입을 나타냄  ex) image/jpeg , image/png,  video/mp4
● fileName: 파일의 이름을 나타냄 ( 디바이스에 저장된 파일 이름을 그대로 반환)
● fileSize: 파일의 크기를 바이트 단위로 나타냄
● duration: 비디오 파일에서, 비디오의 길이를 초 단위로 나타냄
 
 
이제 간단히 image-picker의 사용법에 대해 알았으니 react-native-fs와 같이 활용하는 방법에 대해 설명해 보도록 하겠다.
 
 
 
 
 
 

1. image-picker로 불러온 이미지를 파일로 저장

 
 
위에서 언급했듯이 현재 서버로 JSON 데이터와 이미지 파일 모두 파일형태로 보내야 한다. 그렇기에 아래 예제처럼 imaga-picker에서 디바이스 앨범에 있는 이미지를 불러올 때 해당 이미지를 react-native-fs 라이브러리의 내장 함수인 copyFile을 활용해 파일로 저장하도록 하자. (주석 설명참고)
 
 

import {launchImageLibrary} from 'react-native-image-picker';
import RNFS from 'react-native-fs';

    addImage = () => {
        const options = {												// 파일 업로드 용량 초과 에러를 방지하기 위래 맥시멈 크기 세팅
            mediaType: 'photo',
            quality: 1, 
            maxWidth: 300, 
            maxHeight: 300, 
            includeBase64: false, 
        }
        launchImageLibrary(options, response => {									// image-picker 라이브러리르 통해 이미지 선택
                if (response.didCancel) {
                    console.log('사용자가 ImagaPicker를 취소했습니다.');
                } else if (response.error) {
                    console.log('ImagePicker내에서 에러가 발생했습니다: ', response.error);
                } else if (response.customButton) {
                    console.log('사용자가 custom버튼을 눌렀습니다: ', response.customButton);
                } else {

                    const { uri, type, fileName } = response.assets[0];							// 이미지가 잘 선택되었는지 확인하기 위해 속성값들 담아둘 변순 선언
                    const newFilePath = `${RNFS.TemporaryDirectoryPath}/${fileName}`;					// 이미지를 저장할 파일경로 TemporaryDirecrotyPath메서드를 활용해 세팅
                    const fileUri = `file://${newFilePath}`								// 안드로이드 디바이스이기에 경로 세팅시 앞에 file:// 붙여주기 
                    console.log(`Uri: ${uri}\ \n Type: ${type} \n Name: ${fileName} \n fileUri: ${fileUri}`);		// console로 이미지가 잘 선택되었는지 확인해주기
                    
                    RNFS.copyFile(uri, newFilePath)									// copyFile을 통해 파일시스템에 파일형태로 이미지를 저장
                    .then(() => {
                      this.setState(prevState => ({									// imageUri를 관리하는 state객체의 데이터에 해당 이미지 추가하기
                          imageUri: [...prevState.imageUri, fileUri], 
                          imageType: type,
                          imageName: fileName,
                      }));
                    })
                    .catch(err => console.error('File Copy Error:', err));						// 파일시스템에 파일형태로 이미지저장시 발생하는 에러들 에외처리
                  };
            });
    };

 
 
 
 
 
 

2. Fetch api와 FormData를 활용하여 서버에 데이터 보내기

 
이미지를 파일 형태로 저장을 맞췄으니 이제 dto 객체의 Content-Type을 'application/json'로 지정해 주기 위해 JSON데이터를 파일로 변환한 뒤, multipart/form-data 형태로 서버에 보내주기 위해 FormData 객체를 생성하여 formData에 해당 파일들을 담아 각각 Type을 명시해 준 다음 대부분의 최신 api와 node.js, react-native 환경에서 기본적으로 지원해 주는 HTTP통신 api인 fetch api를 활용해 서버에 post요청을 보내주도록 하자. (주석 설명 참고 + 글쓴이는 fetch api로는 제대로 보내지지만 axios를 활용하면 네트워크 에러가 발생하여 fetch api를 활용해 구현함)
 
 

import { getToken } from './token'
import RNFS from 'react-native-fs';

 async postHouseData() {        		// 숙소등록시 숙소와 관련된 데이터들을 서버에 보내는 함수
        try {   
          const {                   		// 테스트하기위해 임의로 서버에 보내야하는 데이터 세팅
            hostName,
            introText,
            phoneNumber,
            freeService,
            price,
            address,
            maximumGuestNumber,
            imageUri,
            imageType,
            imageName,
          } = this.state;
    
          const formData = new FormData();      // fromData를 사용하기위해 FormData객체를 선언해주기
    
          const dto = {				// 서버에 보내야하는 데이터 타입 맞춰주기
            hostName,
            houseIntroduction: introText,
            freeService,
            phoneNumber,
            registrantId: 1,
            pricePerNight: Number(price.replace(/\D/g, '')),
            address,
            maxNumPeople: Number(maximumGuestNumber.replace(/\D/g, ''))
          };
    
         const jsonString = JSON.stringify(dto);		// dto 객체를 JSON형태의 문자열로 변환
         const fileName = 'dto.json';			
         const filePath = `${RNFS.TemporaryDirectoryPath}/${fileName}`;		// 파일을 저장할 임시 경로지정

         await RNFS.writeFile(filePath, jsonString, 'utf8');		// react-nativev-fs 라이브러리의 writeFile을 활용해 문자열을 파일 형태로 저장

         formData.append('dto', {			// formData에 JSON형태의 문자열 파일을 저장하며 서버에 보내기 위해 Content-Type을 'application/json'으로 지정해주기
             uri: `file://${filePath}`,
             type: 'application/json',		// 이렇게 지정해줘야 다른 default값 형태로 바뀌지않고 mutilpart/form-data 형태로 제대로 보내지
             name: fileName
         });

          this.state.imageUri.forEach((uri, index) => {			// imageUri 배열에 이미지들이 제대로 추가되었는지 테스트
            RNFS.stat(uri)
                .then((stats) => {
                    console.log(`Image ${index}:`, stats);
                    if (stats.isFile()) {
                        console.log(`파일이 저장된 Uri: ${uri}`);
                        console.log(`파일크기: ${stats.size} bytes`);
                        console.log(`최근 수정일: ${stats.mtime}`);
                        console.log(`파일 존재여부: ${stats.isFile()}`);
                    }
                })
                .catch((error) => {
                    console.error(`Error retrieving file stats for URI ${uri}:`, error);
                });
          });
   
          imageUri.forEach((filePath, index) => {			// 파일로 변환해둔 image들을 formData에 추가
            formData.append('photos', {
                uri: filePath,
                name: `image-${index}.jpg`,
                type: imageType,
            });
          });

          for (let pair of formData._parts) {				// 현재 formData에 어떤 값들이 들었는지 console을 활용해 직접적으로 확인이 불가능하니 forEach문을 활요해 순회화며 확인하기
            console.log(pair[0] + ': ' + JSON.stringify(pair[1]));
          }

          const token = await getToken();				// 서버 접근에 필요한 엑세스토큰 불러오기
    
          const response = await fetch('http://223.130.131.166:8080/api/v1/house', {			//fetch api를 활용해 서버에 post 요청 보내기
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${token}`,
                // 'Content-Type': 'multipart/form-data',
            },
            body: formData,
        });
            const responseData = await response.json();
            console.log("Response JSON:", responseData);
        } catch (error) {
          console.log('숙소 데이터 보내는 도중 에러발생:', error);
          if (error.response) {
            console.log('Error status:', error.response.status);
            console.log('Error data:', error.response.data);
            console.log('Error headers:', error.response.headers);
          } else if (error.request) {
            console.log('Request that triggered error:', error.request);
          } else {
            console.log('Error message:', error.message);
          }
        }
      }

 
 
 
 
 
 
+ 나중에 알게 된 사실이지만 image-picker의 속성을 활용해 uri와 type, name 값을 받아 파일라이브러리를 활용하지 않고 바로 form data에 아래 코드처럼 추가해도 type형식 때문에 파일 형태로 날아간다는 것을 알게 되었다 ( 오류의 늪에서 3주 동안 허우적 댔더니 이것저것 확실하게 형태를 변환하지 않아서 multipart형태로 안 보내지는 건 줄 알고 얼탔었던,,)
 
 
 
 

addImage = () => {
...
    launchImageLibrary(options, response => {
    ...
            const { uri, type, fileName } = response.assets[0];
            // 라이브러리를 활용한 copyFile 메서드 없이 속성으로만 form data에 담아도 multipart/form-data 형태로 제대로 보내짐
                 this.setState(prevState => ({
                  imageUri: [...prevState.imageUri, fileUri], 
                  imageType: type,
                  imageName: fileName,
              }));
};
    
async postHouseData() {       
    try {   
        ...
          imageUri.forEach((filePath, index) => {		//image-picker의 속성값을 바로 formData에 담아도 multipart/form-data 형태로 제대로 보내짐
            formData.append('photos', {
                uri: filePath,
                name: `image-${index}.jpg`,
                type: imageType,
            });
          });
		...
    } catch (error) {
		...
    }
}

 
 
 
 
 
 

실제 작성 코드

 
 
 
아래 코드는 실제로 글쓴이가 현재 프로젝트에 작성한 JSON 데이터와 이미지파일을 서버에 전송하는 코드이다.
 
 

import React, {Component} from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Image, ScrollView } from 'react-native';
import {launchImageLibrary} from 'react-native-image-picker';
import { getToken } from './token'
import RNFS from 'react-native-fs';



class AxiosTest extends Component {

    state = {					// 테스트하기위해 임의로 서버에 보내야하는 데이터 세팅
        hostName: '이민호', 
        introText: '테스트',
        freeService: '#와이파이',
        address: '경기도 용인시 수지구',
        phoneNumber: '010-1234-5678',
        price: '13600',
        maximumGuestNumber: '2',
        imageUri: [],
        imageType: null, 
        imageName: null,
    }
 async postHouseData() {        		// 숙소등록시 숙소와 관련된 데이터들을 서버에 보내는 함수
        try {   
          const {                   		// 테스트하기위해 임의로 서버에 보내야하는 데이터 세팅
            hostName,
            introText,
            phoneNumber,
            freeService,
            price,
            address,
            maximumGuestNumber,
            imageUri,
            imageType,
            imageName,
          } = this.state;
    
          const formData = new FormData();      // fromData를 사용하기위해 FormData객체를 선언해주기
    
          const dto = {				// 서버에 보내야하는 데이터 타입 맞춰주기
            hostName,
            houseIntroduction: introText,
            freeService,
            phoneNumber,
            registrantId: 1,
            pricePerNight: Number(price.replace(/\D/g, '')),
            address,
            maxNumPeople: Number(maximumGuestNumber.replace(/\D/g, ''))
          };
    
         const jsonString = JSON.stringify(dto);				// dto 객체를 JSON형태의 문자열로 변환
         const fileName = 'dto.json';			
         const filePath = `${RNFS.TemporaryDirectoryPath}/${fileName}`;		// 파일을 저장한 임시 경로지정

         await RNFS.writeFile(filePath, jsonString, 'utf8');			// react-nativev-fs 라이브러리의 writeFile을 활용해 문자열을 파일 형태로 저장

         formData.append('dto', {					// formData에 JSON형태의 문자열 파일을 저장하며 서버에 보내기 위해 Content-Type을 'application/json'으로 지정해주기
             uri: `file://${filePath}`,
             type: 'application/json',					// 이렇게 지정해줘야 다른 default값 형태로 바뀌지않고 mutilpart/form-data 형태로 제대로 보내지
             name: fileName
         });

          this.state.imageUri.forEach((uri, index) => {			// imageUri 배열에 이미지들이 제대로 추가되었는지 테스트
            RNFS.stat(uri)
                .then((stats) => {
                    console.log(`Image ${index}:`, stats);
                    if (stats.isFile()) {
                        console.log(`파일이 저장된 Uri: ${uri}`);
                        console.log(`파일크기: ${stats.size} bytes`);
                        console.log(`최근 수정일: ${stats.mtime}`);
                        console.log(`파일 존재여부: ${stats.isFile()}`);
                    }
                })
                .catch((error) => {
                    console.error(`Error retrieving file stats for URI ${uri}:`, error);
                });
          });
   
          imageUri.forEach((filePath, index) => {			// 파일로 변환해둔 image들을 formData에 추가
            formData.append('photos', {
                uri: filePath,
                name: `image-${index}.jpg`,
                type: imageType,
            });
          });

          for (let pair of formData._parts) {				// 현재 formData에 어떤 값들이 들었는지 console을 활용해 직접적으로 확인이 불가능하니 forEach문을 활요해 순회화며 확인하기
            console.log(pair[0] + ': ' + JSON.stringify(pair[1]));
          }

          const token = await getToken();				// 서버 접근에 필요한 엑세스토큰 불러오기
    
          const response = await fetch('http://223.130.131.166:8080/api/v1/house', {			//fetch api를 활용해 서버에 post 요청 보내기
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${token}`,
                // 'Content-Type': 'multipart/form-data',
            },
            body: formData,
        });
            const responseData = await response.json();
            console.log("Response JSON:", responseData);
        } catch (error) {
          console.log('숙소 데이터 보내는 도중 에러발생:', error);
          if (error.response) {
            console.log('Error status:', error.response.status);
            console.log('Error data:', error.response.data);
            console.log('Error headers:', error.response.headers);
          } else if (error.request) {
            console.log('Request that triggered error:', error.request);
          } else {
            console.log('Error message:', error.message);
          }
        }
      }
    

    addImage = () => {					// 앨범에서 이미지를 선택해 해당이미지를 파일시스템에 파일객체로 저장하는 함수
        const options = {				// 이미지 파일 전송시 최대용량 초과 에러방지를 위한 이미지 옵션세팅
            mediaType: 'photo',
            quality: 1, 
            maxWidth: 300, 
            maxHeight: 300, 
            includeBase64: false, 
        }
        launchImageLibrary(options, response => {		// image-picker 라이브러리를 통해 이미지를 선택하는 함수
                if (response.didCancel) {
                    console.log('사용자가 ImagaPicker를 취소했습니다.');
                } else if (response.error) {
                    console.log('ImagePicker내에서 에러가 발생했습니다: ', response.error);
                } else if (response.customButton) {
                    console.log('사용자가 custom버튼을 눌렀습니다: ', response.customButton);
                } else {

                    const { uri, type, fileName } = response.assets[0];			// 선택한 이미지의  uri,type,fileName을 setState에 저장하기 위한 임시 변수선언
                    const newFilePath = `${RNFS.TemporaryDirectoryPath}/${fileName}`;
                    const fileUri = `file://${newFilePath}`
                    console.log(`Uri: ${uri}\ \n Type: ${type} \n Name: ${fileName} \n fileUri: ${fileUri}`);
                    
                    RNFS.copyFile(uri, newFilePath)				// copyFile메서드를 통해 이미지를 파일형태로 파일시스템에 저장
                    .then(() => {
                      this.setState(prevState => ({				// 선택한 이미지의  uri,type,fileName을 setState에 저장
                          imageUri: [...prevState.imageUri, fileUri], 
                          imageType: type,
                          imageName: fileName,
                      }));
                    })
                    .catch(err => console.error('File Copy Error:', err));		// 이미지를 파일형태로 저장하는 과정에서 생기는 에러에 대한 예외처리
                  };
            });
    };
    

  render() {			// 사진추가버튼과 서버에 post 요청을 보낼 숙소버튼, 선택이미지들이 제대로 렌더링되는지 테스트
            <View style={styles.container}>
                <View style={styles.houseIMGView}>
                    <TouchableOpacity style={styles.ModifySelectView} onPress={this.addImage}>
                        <Text style={styles.InfoModify}> 사진 추가 </Text>
                    </TouchableOpacity>
                <ScrollView style={styles.addHouseIMGView}  
                    showsHorizontalScrollIndicator={false}  
                    horizontal={true}>
                        {this.state.imageUri.length > 0 ? (
                            this.state.imageUri.map((uri, index) => (
                                <Image 
                                    key={index}
                                    style={styles.houseIMG} 
                                    source={{ uri: uri }}  
                                />
                            ))
                        ) : (
                            <Text>이미지가 아직 로드되지 않았습니다.</Text>
                        )}
                </ScrollView>
                </View>
                <TouchableOpacity style={styles.reservationBtn} onPress={() => this.postHouseData()}>
                    <Text style={styles.InfoModify}> 숙소 등록하기</Text>
                </TouchableOpacity>
            </View>
    )
  }
}

// 스타일 시트
const styles = StyleSheet.create({
    container : {                
        alignItems: 'center', 
        justifyContent: "center",
        height: '100%',
    },
    houseIMGView : {
        alignItems: 'center', 
        justifyContent: "center",
        width: "96%",
        // backgroundColor: 'gray',
    },
    InfoModify: {
        fontSize: 30,
        margin: 40,
    },
    houseIMG: {
        height: 200,
        width: 200,
    },
});

export default AxiosTest;

 
 
 
 
 

 
 
 
 

 
 
 
 
 
 
 
 
 

반응형