[앱개발 종합]리액트-네이티브 Part.1
2주차. 리액트-네이티브로 앱개발하기
<리액트-네이티브란?>
리액트 네이티브는 우리가 배운 자바스크립트 언어 하나로 안드로이드 앱과 iOS앱 두 가지 모두 만들어주는 라이브러리(개발 도구) 입니다.
<Expo란?>
리액트 네이티브 앱 개발을 수월하게 도와주는 도구가 존재하는데, 이것이 바로 Expo입니다. 리액트 네이티브로 앱을 개발할 때, 안드로이드 & iOS 코드를 건드려야 하는 대부분의 상황들을 안 건드려도 되게끔 도와주는 툴입니다. 또한 앱 개발을 편리하게 해주는 도구들이 많이 존재합니다.
Expo를 이용한 앱 개발 순서
1. Expo명령어 설치하기
2. 로컬에 Expo계정 세팅하기
3. expo init 명령어로 기본앱 생성하기
: 터미널에서 expo init 명령어로 프로젝트를 생성하면 Expo로 부터 기본 골격이 짜여져 있는 코드가 생성되는데, 각 파일들을 간단히 알아보자면.. asset - 앱이 동작되는데 기본적으로 가지고 있는 이미지나 아이콘을 담는 폴더, nodes_modules - 앱 개발을 하면서 설치할 라이브러리들이 저장되는 장소, App.js - 앱의 메인 화면, app.json - 앱이 가지는 기본 정보들을 설정하는 파일
4. expo start로 Expo 앱 실행하기
: 터미널에서 expo start 명령어를 실행해주면 QR코드가 생성됩니다.
5. 휴대폰에 설치한 Expo 클라이언트 앱으로 Expo 앱 실행
휴대폰으로 QR코드를 인식시키면 바로 앱 화면이 생성되는 것을 확인할 수 있습니다.
추가) App.js로 개발하다보면 노란 경고창이 뜰 때가 있는데, 이 때 App.js에 다음과 같은 코드 한줄을 통해 안보이게 할 수 있습니다.
console.disableYellowBox = true;
<JSX 문법>
HTML태그와 비슷하게 생긴 JSX는 App.js의 화면을 구성하는데 필요한 태그 문법입니다. 리액트 네이티브에서 return은 작성한 JSX문법으로 구성된 화면을 앱에 보여주는 역할을 하는 겁니다. (렌더링)
1. <View></View>
: 화면의 영역(레이아웃)을 잡아주는 엘리먼트 입니다. App.js에서 View는 화면 전체 영역을 가지게 됩니다. View를 통 해 화면을 분할하기도 하는데 이때는 StyleSheet가 필요하답니다.
2. <Text></Text>
: 앱에 텍스트를 작성하려면 반드시 사용해야 하는 엘리먼트입니다.
3. <ScrollView</ScrollView>
: 앱은 보통 많은 양의 정보를 담고 있습니다. 그래서 앱 화면을 벗어나는 경우가 많은데 이때 ScrollView를 사용하면 스크롤이 가능해지면서 모든 컨텐츠를 볼 수 있습니다.
4. <Button/>
: 앱에서 버튼을 누르면 팝업이 뜨거나, 다른 페이지로 이동하는데 Button을 통해 쉽게 버튼을 만들 수 있습니다.
버튼이 눌리면 팝업이 뜨게 하기 위해서는 onPress속성을 통해 팝업이 뜨게 할 수 있습니다.
onPress={function(){ //onPress={()=>{
Alert.alert('팝업 알람입니다!!')
}}
5. <TouchableOpacity/>
: 영역을 충분히 가지는 텍스트 카드 부분이라고 할 수 있습니다. 버튼의 역할을 하는 이 엘리먼트는 화면의 임의의 영
역과 디자인에 버튼 기능을 할 때 사용합니다.
6. <Image>
: 앱에 이미지를 삽입할 때 사용하는 태그로, 두가지 방식으로 이미지를 사용할 수 있습니다.
- assets폴더에 있는 이미지를 가져와서(import) 사용하는 방법 ex) import favicon from "./assets/favicon.png"
- 외부 이미지의 uri를 넣어서 사용하는 방법
<JSX문법 - StyleSheet>
구성한 앱화면을 꾸미기 위한 StyleSheet은 결국 객체(딕셔너리)를 하나 만드는데, 이쁜 옷들을 입혀주는 객체 입니다. 이 객체에 옷을 사용법대로 생성한 다음 잘 정리해 두고, JSX 엘리먼트에서 사용합니다. 각각의 태그별로 여러 Style을 가지고 있기 때문에 중요한 것 위주로 설명하고, 나머지는 직접 구성하면서 설명해보도록 하겠습니다.
1. margin과 padding
: margin과 padding은 다음 이미지들 처럼, 영역의 바깥과 안의 여백을 결정합니다. margin은 어느정도 여백을 두고 배치할 것인지를 의미하고, padding은 어느정도의 여백을 두어 내부의 요소를 위치시킬 것인지를 결정합니다.
2. 컨텐츠의 위치 : flex
: flex는 영역을 차지하는 속성인데, 레이아웃을 상대적으로 결정하게 됩니다. 만약 전체 화면이 1이고 A가 1, B가 2ㅇ 니 경우에 A는 전체화면의 1/3을 차지하고, B는 전체화면의 2/3를 차지하게 되는 것입니다. 즉, 같은 레벨의 엘리먼 트들의 flex 합을 각자의 flex 속성값 대로 가져갑니다.
3. flexDirection
: 자리잡은 영역의 방향을 의미합니다. flexDirection:"row"는 가로 방향으로 , "colum"은 세로 방향으로 영역을 배치합 니다. 기본값은 colum입니다.
4. justifyContent
: justifyContent는 flexDirection과 동일한 방향으로 정렬하는 속성입니다 flexDirection: 'column'에서 justifyContent는 상하 정렬, flexDirection: 'row'에서 justifyContent는 좌우 정렬을 뜻합니다. flex-start, center, flex-end, space- between, space-around 속성을 가집니다.
5. alignItems
: Align Items는 Flex Direction과 수직한 방향(반대 방향이라고 생각하면 편합니다)으로 정렬하는 속성입니다. flexDirection: 'column'에서 alignItems는 좌우 정렬, flexDirection: 'row'에서 alignItems는 상하 정렬을 뜻합니다 flex-start, center, flex-end, stretch 속성을 가집니다.
<메인 화면 꾸미기>
import React from 'react';
import { StyleSheet, Text, View, ScrollView, Image, TouchableOpacity } from 'react-native';
export default function App() {
return (
<ScrollView style={styles.container}>
<Text style={styles.textStyle}>나만의 꿀팁</Text>
<Image
source={{uri:'https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmain.png?alt=media&token=8e5eb78d-19ee-4359-9209-347d125b322c'}}
// 사용설명서에 나와 있는 resizeMode 속성 값을 그대로 넣어 적용합니다
resizeMode={"cover"}
style={styles.mainImage}
/>
<ScrollView style={styles.menucontainer} horizontal indicatorStyle={"white"} > //스크롤 뷰를 가로로 하기 위한 속성
<TouchableOpacity style={styles.textContainer1}>
<Text style={styles.menu_textStyle}>생활</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.textContainer2}>
<Text style={styles.menu_textStyle}>재테크</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.textContainer3}>
<Text style={styles.menu_textStyle}>반려견</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.textContainer4}>
<Text style={styles.menu_textStyle}>꿀팁 찜</Text>
</TouchableOpacity>
</ScrollView>
<View style={styles.feed}>
<Image style={styles.feedImage} source={{uri:"https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fpizza.png?alt=media&token=1a099927-d818-45d4-b48a-7906fd0d2ad3"}}/>
<View style={styles.feedText}>
<Text style={styles.feedTitle}>먹다 남은 피자를 촉촉하게!</Text>
<Text style={styles.feedInfo} numberOfLines={3}>먹다 남은 피자는 수분이 날라가기 때문에 처음처럼 맛있게 먹을 수 없는데요. 이럴 경우 그릇에 물을 받아 전자레인지 안에서 1분 30초에서 2분 정도 함께 돌려주면 촉촉하게 먹을 수 있습니다. 물이 전자레인지 안에서 수증기를 일으키고, 피자에 촉촉함을 더해줍니다.</Text>
<Text style={styles.feedDate}>2020.09.09</Text>
</View>
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#fff',
},
textStyle: {
color:"black",
fontSize:20,
fontWeight:"700",
marginLeft:20,
marginTop:50
},
mainImage: {
width:'90%',
height:200,
borderRadius:10,
marginTop:20,
alignSelf:"center"
},
menucontainer: {
marginTop:20
},
textContainer1: {
width:100,
height:60,
backgroundColor:'#fdc453',
borderRadius:15,
margin:10,
padding:20
},
textContainer2: {
width:100,
height:60,
backgroundColor:'#fe8d6f',
borderRadius:15,
margin:10,
padding:20
},
textContainer3: {
width:100,
height:60,
backgroundColor:'#9adbc5',
borderRadius:15,
margin:10,
padding:20
},
textContainer4: {
width:100,
height:60,
backgroundColor:'#f886a8',
borderRadius:15,
margin:10,
padding:20
},
menu_textStyle: {
color:"white",
//글자의 크기를 결정합니다
fontSize:15,
fontWeight:"700",
textAlign:'center'
},
feed:{
marginTop:20,
flex:1,
flexDirection:"row",
borderBottomWidth:1,
borderBottomColor:"gray",
paddingBottom:10
},
feedImage: {
flex:1,
width:100,
height:100,
borderRadius:10,
},
feedText: {
flex:2,
flexDirection:"column",
marginLeft:10,
},
feedTitle: {
fontSize:20,
fontWeight:"700"
},
feedInfo: {
fontSize:15
},
feedDate: {
fontSize:10,
color:"gray",
}
});
<앱과 자바스크립트>
-모듈과 반복문 (data.json)
위에서는 피드(카드)에 해당하는 내용을 직접적으로 넣어주고 있는데 이 데이터가 많다면 어떻게 할까요? data.json에 준비된 데이터가 존재한다면 반복문을 통해 담겨져 있는 데이터를 사용할 수 있습니다.
//data.json
{
"tip":[
{
"idx":0,
"category":"생활",
"title":"먹다 남은 피자를 촉촉하게!",
"image":"https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fpizza.png?alt=media&token=1a099927-d818-45d4-b48a-7906fd0d2ad3",
"desc":"먹다 남은 피자는 수분이 날라가기 때문에 처음처럼 맛있게 먹을 수 없는데요. 이럴 경우 그릇에 물을 받아 전자레인지 안에서 1분 30초에서 2분 정도 함께 돌려주면 촉촉하게 먹을 수 있습니다. 물이 전자레인지 안에서 수증기를 일으키고, 피자에 촉촉함을 더해줍니다.",
"date":"2020.09.09"
},
....
}
준비된 데이터를 App.js에서 불러오기 위해서는 "import data from './data.json';"로 불러오게 됩니다. 이렇게 불러온 데이터를 내가 구성하고 싶은대로 구성하기 위해 반복문을 사용하는데 지난 시간에 학습했던 map을 이용하면 다음과 같이 코드를 구성할 수 있습니다.
//App.js
<View style={styles.cardContainer}>
{/* 하나의 카드 영역을 나타내는 View */}
{
tip.map((content,i)=>{
return (<View style={styles.card} key={i}>
<Image style={styles.cardImage} source={{uri:content.image}}/>
<View style={styles.cardText}>
<Text style={styles.cardTitle} numberOfLines={1}>{content.title}</Text>
<Text style={styles.cardDesc} numberOfLines={3}>{content.desc}</Text>
<Text style={styles.cardDate}>{content.date}</Text>
</View>
</View>)
})
}
</View>
-{}표현식
export default function App() {
console.disableYellowBox = true;
//return 구문 밖에서는 슬래시 두개 방식으로 주석
let tip = data.tip;
let todayWeather = 10 + 17;
let todayCondition = "흐림"
return (
<ScrollView style={styles.container}>
<Text style={styles.title}>나만의 꿀팁</Text>
<Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> //{}표현식 사용
<Image style={styles.mainImage} source={main}/>
-if문
if문, 조건문을 통해서 홀수의 데이터 혹은 짝수의 데이터만 다르게 보이게 할 수 있습니다. 위에서 작성하였던 코드에 삼항 연산자를 사용하여 홀수 데이터와 짝수 데이터를 구분하는 코드는 다음과 같습니다.
<View style={styles.cardContainer}>
{/* 하나의 카드 영역을 나타내는 View */}
{
tip.map((content,i)=>{
return i % 2 == 0 ? (<View style={styles.cardEven} key={i}> //삼항연산자 사용
<Image style={styles.cardImage} source={{uri:content.image}}/>
<View style={styles.cardText}>
<Text style={styles.cardTitle} numberOfLines={1}>{content.title}</Text>
<Text style={styles.cardDesc} numberOfLines={3}>{content.desc}</Text>
<Text style={styles.cardDate}>{content.date}</Text>
</View>
</View>) : (<View style={styles.cardOdd} key={i}>
<Image style={styles.cardImage} source={{uri:content.image}}/>
<View style={styles.cardText}>
<Text style={styles.cardTitle} numberOfLines={1}>{content.title}</Text>
<Text style={styles.cardDesc} numberOfLines={3}>{content.desc}</Text>
<Text style={styles.cardDate}>{content.date}</Text>
</View>
</View>)
})
}
</View>
<어바웃 화면 만들기>
import React from 'react';
import main from '../assets/main.png';
import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView, Alert, Linking} from 'react-native';
export default function AboutPage() {
console.disableYellowBox = true;
const customAlert = () => {
//Alert.alert("OOO의 인스타계정으로 이동합니다.")
Linking.openURL('https://www.instagram.com/instaId/')
}
return (
<View style={styles.container}>
<Text style={styles.title}>HI! 스파르타코딩 앱개발반에 오신걸 환영합니다</Text>
<View style={styles.textContainer}>
<Image style={styles.Image} source={{uri:"https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4"}}/>
<Text style={styles.context1}>많은 내용을 간결하게 담아내려 노력했습니다!</Text>
<Text style={styles.context2}>꼭 완주하셔서 꼭 여러분 것으로 만들어가시길 바랍니다.</Text>
<TouchableOpacity style={styles.button} onPress={customAlert}><Text style={styles.ButtonText}>OOO의 인스타 계정</Text></TouchableOpacity>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
height: 1000,
backgroundColor: '#260074',
},
title: {
fontSize: 40,
fontWeight: '700',
textAlign:'center',
color:'#fff',
marginTop:100,
marginLeft:15,
marginRight:15
},
textContainer: {
marginTop:50,
marginLeft:30,
marginRight:30,
marginBottom:50,
height:550,
paddingTop: 50,
paddingLeft: 25,
paddingRight: 25,
borderRadius:30,
backgroundColor:"#fff",
},
Image: {
width:'50%',
height:150,
borderRadius:25,
marginTop:20,
alignSelf:"center"
},
context1: {
fontSize: 25,
fontWeight: '700',
textAlign:'center',
color:'#000',
marginTop:20,
},
context2: {
fontSize: 18,
fontWeight: '700',
textAlign:'center',
color:'#000',
marginTop:20,
},
button: {
width:200,
height:70,
padding:20,
backgroundColor:"#fdc453",
borderRadius:15,
margin:30,
alignSelf:"center"
},
ButtonText: {
color:"#fff",
fontSize: 18,
fontWeight:"700",
textAlign:"center"
},
});