Expo 시작
설치
Expo 설치 전 설치되어 있어야 하는 것: Node & NPM 필수! +) yarn
Expo 를 이용하기 위한 명령어 도구를 설치해준다.
npm install -g expo-cli
컴퓨터에 Expo 를 설치해주었다면 개발한 앱을 바로 확인할수 있는 클라이언트 앱 Expo 를 핸드폰에도 받아둔다.
핸드폰에도 설치가 되었다면 Expo 계정을 만들어주자. (가입 링크)
이제 계정도 만들어주었다면 로컬에 Expo 계정을 세팅해보자!
이것도 터미널/cmd 창에서 입력해주면 된다.
expo login
...
expo 이메일(또는 아이디), 패스워드 입력란이 차례로 나오고, 차례대로 입력하면 로그인 성공!
생성
로그인까지 해주었다면 expo 명령어를 입력해보자.
expo 시작하기
프로젝트 폴더에서 해당 명령어를 입력해준다.
expo init sparta-myhoneytip-gun(이부분은 생성할 폴더이름)
명령어를 입력하면 터미널에서 어떤 유형의 Expo 앱을 만들어줄까? 하고 물어본다.
빈 스케치북을 달라는 의미로 blank 가 선택된 상태에서 엔터로 선택해주면 된다.
🚨 윈도우의 경우 VSCode 터미널에서 처음에 그냥 expo init 을 했을 때 expo: 이 시스템에서 스크립트를 실행할 수 없으므로 ~~ 파일을 로드할 수 업습니다. 와 같은 오류가 뜰 수 있다.
VSCode 의 기본 터미널이 shell 로 되어 있는데, 아마도 명령에 대한 권한 오류인 것 같다.
👉 VSCode 의 기본 터미널을 shell 에서 cmd 로 바꿔주고 터미널에서 명령어를 다시 입력해주면 된다.
그냥 cmd 를 입력하면 cmd 터미널로 바뀌긴 하는데 매번 바꿔주기 귀찮으니까 기왕이면 기본 터미널을 cmd 로 바꿔주는 것이 좋다!
💡 VSCode 기본 터미널 설정 바꾸기
1. VSCode를 실행하고 Ctrl+Shift+P 조합키를 입력한다. (모든 설정 검색창)
2. "shell" 이라고 입력한다.
3. "Terminal: Select Default Shell" 을 클릭한다.
4. "Command Prompt C:\Windows\System32\cmd.exe" 를 클릭한다.
5. VSCode를 재실행하면 제일 아래 "PS"로 시작하던 것이 없어졌을 것이고 이는 CMD로 바뀌었다는 의미가 된다.
실행
expo start
*이전 버전에서는 expo start 이후 바로 웹이 켜지면서 확인이 가능했는데 6.0 버전 이후로는 웹에서 확인이 안된다고 한다!
Expo 개발자도구
- Run on Android device/emulator: 컴퓨터와 USB로 연결된 안드로이드 휴대폰 또는 우리가 설치한 안드로이드 시뮬레이터로 Expo를 실행시키는 버튼
- Run on iOS simulator: 설치한 iOS 시뮬레이터로 Expo 앱을 실행시키는 버튼
- Run in web browser: 작업중인 Expo 앱을 브라우저에서 확인하는 버튼. 즉, 웹 플랫폼에 대응하게끔 지원
- Send link with email: 작업중인 Expo 앱을 Expo 클라이언트 앱이 설치되어 있는 휴대폰 어디서든 실행할 수 있다. 즉 이 때 개발중인 Expo 앱 링크를 통해서도 바로 Expo 클라이언트 앱으로 개발중인 앱 확인이 가능하다.
- Public or republish project: 앱을 내 계정의 Expo 공식사이트에 업로드한다. Expo 공식 사이트에 개발한 앱을 배포하게 되면, 사이트에서 안드로이드용 APK 파일 혹은 IOS용 ipa 파일을 빌드하여 다운로드 받을 수 있다. 그럼 우린 이걸 가지고 직접 안드로이드 마켓 혹은 애플 스토어에 앱을 출시할 수 있다.
Expo 프로젝트 기본 폴더 구조
- assets: 앱이 동작되고 서비스되는데에 기본적으로 가지고 있는 이미지 및 아이콘 파일들을 담는 폴더
- node_modules: 리액트 네이티브&Expo로 앱을 만들면서 설치하게 되는 많은 라이브러리들이 저장되는 장소
- App.js: 리액트 네이티브 앱이 시작되는 출발선/진입점 이다. 웹으로 따지면 index.html 처럼 메인 파일이라고 생각하면 편하다! 여기서 앱이 시작될 때 필요한 준비들(필요한 이미지, 폰트 준비)을 하는 장소이며, 준비를 할 때 준비중입니다~ 라는 화면을 띄워주는 곳이다. 준비가 끝나면, 본 화면을 보여준다.
- app.json: 앱의 이름, 앱의 출시 버전, 앱이 휴대폰에 설치될 때 보여질 아이콘, 앱이 켜질 때 보여지는 스플래시 스크린 화면, 안드로이드 또는 IOS 각각의 광고 설정 등 앱이 가지는 기본 정보들을 설정하는 파일이다.
앱 화면 만들기
Expo 경고창 지우기
import React from 'react';
import { LogBox } from 'react-native';
export default function App() {
LogBox.ignoreLogs(['Warning: ...']);
return ();
}
JSX 기본 문법
App.js 는 JSX문법으로 그려져 준비된 화면을 반환한다.
<View> <Text> 와 같이 꺽쇠로 쓰여져 있는 것들을 리액트 네이티브에서 JSX 라고 부른다.
JSX 문법상의 꺽쇠를 태그라고 부르고, <View> 영역 </View> 와 같이 닫는 태그로 온전히 화면의 한 영역을 구성할 때 JSX에서 엘리먼트 라고 부른다.
✅ 1. 모든 태그는 가져와서 사용한다.
앞으로 사용할 태그들, View 나 Text 같은 문법은 임의로 만든 태그들이 아니다.
리액트 네이티브에서 제공해주는, 이미 존재하는 태그 문법을 가져와서 사용하는 것이다. (공식 문서)
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native'; // 가져온다
export default function App() {
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<StatusBar style="auto" />
</View>
);
}
✅ 2. 태그는 항상 닫는 태그와 자체적으로 닫는 태그를 구분해서 사용해야 한다!
export default function App() {
return (
//<View>는 결국 두번째 줄 밑에 </View>로 닫히면서 본인 영역을 갖습니다
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
// statusBar는 본인 스스로 닫는 태그이므로 다음과 같이 사용이 가능합니다.
<StatusBar style="auto" />
</View>
);
}
어떤 태그는 닫고 어떤 태그는 스스로 닫히는지 어떻게 알까? 리액트 네이티브 / Expo 공식 문서들을 참고하자!
✅ 3. 모든 엘리먼트는 감싸는 최상위 엘리먼트가 있어야 한다. 엘리먼트는 태그 <> 를 뜻한다!
//App.js가 렌더링 하고 엘리먼트는 결국
//Text와 StatusBar엘리먼트를 감싸고 잇는 View입니다.
export default function App() {
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<StatusBar style="auto" />
</View>
);
}
다음과 같이 감싸는 엘리먼트가 없다면 오류가 발생한다.
//View 엘리먼트 밖에 StatusBar가 나와 있으므로 엘리먼트 전체를 감싸는 엘리먼트가
//없어서 오류가 납니다.
export default function App() {
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
</View>
<StatusBar style="auto" />
);
}
꼭 감싸는 엘리먼트 없이, 혹은 추후 디자인적인 측면을 위해 없이 진행해야 한다면, 다음과 같이 프래그먼트(<></>)라는 의미없는 엘리먼트로 감싸서 렌더링 할 수도 있지만 이 방법은 지양해야하는 방식이다.
export default function App() {
return (
<>
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
</View>
<StatusBar style="auto" />
</>
);
}
✅ 4. return 에 의해 렌더링 될 땐 항상 소괄호로 감싸져야 한다.
export default function App() {
return (
);
}
✅ 5. JSX 문법 밖에서의 주석과 안에서의 주석은 다르다.
//JSX밖에서의 주석
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
//JSX밖에서의 주석
export default function App() {
//JSX밖에서의 주석
return (
//JSX 밖에서의 주석
<View style={styles.container}>
{/*
JSX 문법 안에서의 주석
*/}
<Text>Open up App.js to start working on your app!</Text>
<StatusBar style="auto" />
</View>
);
}
//JSX밖에서의 주석
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
<View>
화면의 영역(레이아웃)을 잡아주는 엘리먼트이다. 이 View 엘리먼트로 다음과 같이 화면을 원하는대로 분할 할 수도 있다.
<Text>
앱에 글을 작성하려면 반드시 사용해야하는 엘리먼트이다.
만약 View 와 같은 엘리먼트 안에서 문자를 작성하게 되면 오류가 발생한다.
<ScrollView>
앱 화면을 벗어나는 영역의 경우 ScrollView 엘리먼트로 감싸면 스크롤이 가능해지면서 모든 컨텐츠를 볼 수 있다.
import React from 'react';
import { StyleSheet, Text, View, ScrollView } from 'react-native';
export default function App() {
return (
<ScrollView style={styles.container}>
<View style={styles.textContainer}>
<Text style={styles.textStyle}>영역을 충분히 갖는 텍스트 입니다!</Text>
</View>
<View style={styles.textContainer}>
<Text style={styles.textStyle}>영역을 충분히 갖는 텍스트 입니다!</Text>
</View>
<View style={styles.textContainer}>
<Text style={styles.textStyle}>영역을 충분히 갖는 텍스트 입니다!</Text>
</View>
<View style={styles.textContainer}>
<Text style={styles.textStyle}>영역을 충분히 갖는 텍스트 입니다!</Text>
</View>
<View style={styles.textContainer}>
<Text style={styles.textStyle}>영역을 충분히 갖는 텍스트 입니다!</Text>
</View>
<View style={styles.textContainer}>
<Text style={styles.textStyle}>영역을 충분히 갖는 텍스트 입니다!</Text>
</View>
<View style={styles.textContainer}>
<Text style={styles.textStyle}>영역을 충분히 갖는 텍스트 입니다!</Text>
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
textContainer: {
height:100,
borderColor:'#000',
borderWidth:1,
borderRadius:10,
margin:10,
},
textStyle: {
textAlign:"center"
}
});
위와 같이 ScrollView 엘리먼트로 감싸주면 스크롤이 가능해지면서 많은 컨텐츠들을 확인할 수 있다.
<Button>
title
이라던지 color
속성이라던지 onPress
속성들은 공식 문서에 작혀있는 사용법을 그대로 사용한 것이다.
- 버튼 설명서
🚨 위에 사진에서 M자 탈모가 있는 아이폰의 경우 아래 버튼을 눌러주세요 부분이 카메라가 있는 위치에 가려져서 보이지 않는다..
이런 경우 <SafeAreaView> 를 최상위뷰 태그로 바꿔주면 전체적으로 내려오면서 화면이 조정된다.
<SafeAreaView>
모든 기계에 안전하게 화면을 맞출 필요가 있기 때문에 <SafeAreaView>를 최상위뷰 태그로 바꿔주면 위쪽에 상태바 영역은 무시하고 이외의 직사각형 영역만 고려해서 그 안에서만 화면이 그려지게끔 안전하게 모든 화면을 대응시킬 수 있는 뷰 태그이다.
<TouchableOpacity/>
<Button> 엘리먼트 같은 경우 본인의 영역을 가지기 때문에 스타일에도 신경을 써야하고 <ScrollView> 에서 처럼 카드 형식으로 만든 다음 영역을 충분히 갖는 텍스트 카드 부분을 눌러야하는 경우엔 버튼 태그를 사용하기 어렵다.
이럴 때 사용하는 것이 <TouchableOpacity> 엘리먼트이다. 이 영역은 스타일은 주지 않은 이상 화면에 영향을 주지 않는 영역을 갖는다.
이렇게 임의의 영역과 디자인에 버튼 기능을 달고 싶을 때 주로 사용하게 될 태그이다.
<Image>
이미지를 불러오는 방식에는 두 가지가 있다.
1. assets 폴더에 있는 이미지 가져와서 사용하기 (import)
2. 외부 이미지 사용하기 (uri)
외부 이미지를 사용할 땐 source
부분에 uri
를 사용하면 된다.
StyleSheet
태그에 스타일을 주는 방식 또한 리액트 네이티브에서 제공하는 StyleSheet 기능을 가져와서 적용할 수 있다.
이 StyleSheet 는 결국 객체(딕셔너리)를 하나 만드는데, 이쁜 옷들을 정리해놓는 객체이다.
이 객체에 옷을 사용법대로 생성한 다음 잘 정리해두고, JSX 엘리먼트에서 사용하면 된다.
사용할 땐 모든 태그에 공통적으로 있는 style 속성에 아래서 만든 객체 키값을 부여하여 적용한다.
가장 바깥에 View 태그를 보면 style 속성에 styles 객체 container 키를 연결한 것을 확인할 수 있다.
<View style={styles.container}>
💡 자주 사용하는 스타일 속성
import React from 'react';
import { StyleSheet, Text, View, Image } from 'react-native';
export default function App() {
return (
<View style={styles.container}>
<View style={styles.textContainer}>
<Text style={styles.textStyle}>스파르타 코딩클럽!!</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
//영역을 잡는 속성입니다. 따로 자세히 다룹니다.
//flex: 1은 전체 화면을 가져간다는 뜻입니다
flex: 1,
//영역의 배경 색을 결정합니다
backgroundColor: '#fff',
//아래 두 속성은 영역 안의 컨텐츠들의 배치를 결정합니다.
//flex를 자세히 다룰때 같이 자세히 다룹니다
justifyContent:"center",
alignContent:"center"
},
textContainer: {
//영역의 바깥 공간 이격을 뜻합니다(하단 이미지 참조)
margin:10,
//영역 안의 컨텐츠 이격 공간을 뜻합니다(하단 이미지 참조)
padding: 10,
//테두리의 구부러짐을 결정합니다. 지금 보면 조금 둥글죠?
borderRadius:10,
//테두리의 두께를 결정합니다
borderWidth:2,
//테두리 색을 결정합니다
borderColor:"#000",
//테구리 스타일을 결정합니다. 실선은 solid 입니다
borderStyle:"dotted",
},
textStyle: {
//글자 색을 결정합니다. rgb, 값 이름, 색상코드 모두 가능합니다
color:"red",
//글자의 크기를 결정합니다
fontSize:20,
//글자의 두께를 결정합니다
fontWeight:"700",
//가로기준으로 글자의 위치를 결정합니다
textAlign:"center"
}
});
Flex
앱 화면을 구성할 때 flex 가 가장 중요하고 봐도 과언이 아니다. 영역의 레이아웃을 결정하기 때문이다.
flex 는 상대적인 것으로 같은 레벨의 엘리먼트들의 flex 합을 각자의 flex 속성값대로 가져간다.
즉, flex 는 위치한 곳의 영역을 같은 레벨의 flex 합 비율대로 가져간다!
가장 최상위의 container 스타일을 가져가는 <View> 엘리먼트는 디바이스 전체 화면의 영역을 가져간다.
안 쪽의 containerOne 스타일이 부여된 <View> 엘리먼트는 전체를 3등분한 뒤 1/3을 가져가고 containerTwo 는 2/3을 가져간다.
똑같은 방식으로 다른 예제를 확인해보면
export default function App() {
return (
<View style={styles.container}>
<View style={styles.containerOne}>
</View>
<View style={styles.containerTwo}>
<View style={styles.innerOne}>
</View>
<View style={styles.innerTwo}>
</View>
</View>
</View>
);
}
노란색 영역을 5로 나눈 후, 파랑색 영역이 1/5를, 주황색 영역이 4/5를 가져가게 된다.
flexDirection
자리 잡은 영역의 방향을 나타내는 속성이다.
row 는 가로 방향, column 은 세로방향으로 영역을 배치한다. 기본값은 column 이다.
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default function App() {
return (
<View style={styles.container}>
<View style={styles.containerOne}>
</View>
<View style={styles.containerTwo}>
<View style={styles.innerOne}>
</View>
<View style={styles.innerTwo}>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex:1
},
containerOne: {
flex:1,
backgroundColor:"red"
},
containerTwo:{
flex:2,
flexDirection:"row",
backgroundColor:"yellow"
},
innerOne: {
flex:1,
backgroundColor:"blue"
},
innerTwo: {
flex:4,
backgroundColor:"orange"
}
});
노란색 영역에 넣었던 파랑, 주황 영역이 가로 방향으로 영역을 다시 잡은 모습을 확인할 수 있다.
justifyContent
justifyContent 는 flexDirection 과 동일한 방향으로 정렬하는 속성이다.
- flexDirection: 'column' 에서 justifyContent 는 상하 정렬
- flexDirection: 'row' 에서 justifyContent 는 좌우 정렬 을 뜻한다.
flex-start, center, flex-end, space-between, space-around 속성을 가진다.
상위 엘리먼트 영역의 flexDirection을 따라서 컨텐츠 위치도 변경됨을 알 수 있다.
alignItems
Align Items 는 Flex Direction 과 수직한 방향(반대 방향이라고 생각하면 편하다)으로 정렬하는 속성이다.
- flexDirection: 'column' 에서 alignItems 는 좌우 정렬
- flexDirection: 'row' 에서 alignItems 는 상하 정렬
flex-start, center, flex-end, stretch 속성을 가진다.
이것도 마찬가지로 상위 엘리먼트에 적용을 해야 안에 있는 컨텐츠에 영향이 간다!
메인화면 꾸미기
[실습] 복습/공부 겸 메인화면을 다음 화면처럼 꾸며보자.
실습 예제 파악해보기
- 상단에 '나만의 꿀팁' 제목 텍스트 들어가기
- 제목 바로 밑에 이미지를 해당 이미지를 넣기
- 가운데 버튼은 횡(수평) 스크롤(ScrollView) 기능을 써서 좌우로 스크롤 가능하게 만들어야겠다
- ScrollView 수직 스크롤은 그냥 쓰면 되지만 횡은 어떻게 쓸까? 공식문서 참고 👉 Horizontal
- 주어진 데이터는 문자이므로 Text 태그를 넣자
- 설명 글은 긴데 3줄이 넘어가면 말 줄임표가 되네! Text 태그 속성에 이런 기능이 있나 찾아보자
- 피자 이미지와 글자 부분의 영역을 1:3 정도로 분할하면 되겠다
라이브러리 임포트
import React from 'react';
import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native';
기본 함수 골격
export default function App() {
return ()
}
const styles = StyleSheet.create({})
해설 코드
import React from 'react';
import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native';
const main = 'https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmain.png?alt=media&token=8e5eb78d-19ee-4359-9209-347d125b322c'
export default function App() {
//return 구문 밖에서는 슬래시 두개 방식으로 주석
return (
/*
return 구문 안에서는 {슬래시 + * 방식으로 주석
*/
<ScrollView style={styles.container}>
<Text style={styles.title}>나만의 꿀팁</Text>
<Image style={styles.mainImage} source={{uri:main}}/>
<ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}>
<TouchableOpacity style={styles.middleButton01}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity>
<TouchableOpacity style={styles.middleButton02}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity>
<TouchableOpacity style={styles.middleButton03}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity>
<TouchableOpacity style={styles.middleButton04}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>
</ScrollView>
<View style={styles.cardContainer}>
{/* 하나의 카드 영역을 나타내는 View */}
<View style={styles.card}>
<Image style={styles.cardImage} 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.cardText}>
<Text style={styles.cardTitle}>먹다 남은 피자를 촉촉하게!</Text>
<Text style={styles.cardDesc} numberOfLines={3}>먹다 남은 피자는 수분이 날라가기 때문에 처음처럼 맛있게 먹을 수 없는데요. 이럴 경우 그릇에 물을 받아 전자레인지 안에서 1분 30초에서 2분 정도 함께 돌려주면 촉촉하게 먹을 수 있습니다. 물이 전자레인지 안에서 수증기를 일으키고, 피자에 촉촉함을 더해줍니다.</Text>
<Text style={styles.cardDate}>2020.09.09</Text>
</View>
</View>
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
//앱의 배경 색
backgroundColor: '#fff',
},
title: {
//폰트 사이즈
fontSize: 20,
//폰트 두께
fontWeight: '700',
//위 공간으로 부터 이격
marginTop:50,
//왼쪽 공간으로 부터 이격'
marginLeft:20
},
mainImage: {
//컨텐츠의 넓이 값
width:'90%',
//컨텐츠의 높이 값
height:200,
//컨텐츠의 모서리 구부리기
borderRadius:10,
marginTop:20,
//컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능)
//각 속성의 값들은 공식문서에 고대로~ 나와 있음
alignSelf:"center"
},
middleContainer:{
marginTop:20,
marginLeft:10,
height:60
},
middleButton01: {
width:100,
height:50,
padding:15,
backgroundColor:"#fdc453",
borderColor:"deeppink",
borderRadius:15,
margin:7
},
middleButton02: {
width:100,
height:50,
padding:15,
backgroundColor:"#fe8d6f",
borderRadius:15,
margin:7
},
middleButton03: {
width:100,
height:50,
padding:15,
backgroundColor:"#9adbc5",
borderRadius:15,
margin:7
},
middleButton04: {
width:100,
height:50,
padding:15,
backgroundColor:"#f886a8",
borderRadius:15,
margin:7
},
middleButtonText: {
color:"#fff",
fontWeight:"700",
//텍스트의 현재 위치에서의 정렬
textAlign:"center"
},
cardContainer: {
marginTop:10,
marginLeft:10
},
card:{
flex:1,
//컨텐츠들을 가로로 나열
//세로로 나열은 column <- 디폴트 값임
flexDirection:"row",
margin:10,
borderBottomWidth:0.5,
borderBottomColor:"#eee",
paddingBottom:10
},
cardImage: {
flex:1,
width:100,
height:100,
borderRadius:10,
},
cardText: {
flex:2,
flexDirection:"column",
marginLeft:10,
},
cardTitle: {
fontSize:20,
fontWeight:"700"
},
cardDesc: {
fontSize:15
},
cardDate: {
fontSize:10,
color:"#A6A6A6",
}
});
직접 해보기
- <Image> 의 경우 단순히 source 와 uri 만 입력한다고 이미지가 뜨지 않는다. 구체적인 width/height 값도 지정해주어야 이미지가 렌더링된다!
- 계속 이미지가 안떠서 뭐가 문제인가 했더니 styles={styles.mainImgage} 로 오타가 나있었다. 이거는 오류에도 잡히지도 않더라ㅠㅠ 오타 조심!
- 이미지를 alignSelf: 'center' 라는 속성을 통해 가운데로 정렬할 수 있었다.
- 세로 스크롤이 아닌 가로로 스크롤을 구현하기 위해서는 <ScrollView horizontal> 과 같이 태그에 추가해주면 된다!
- <ScrollView indicatorStyle={"white"} > 와 같이 작성하면 스크롤바 색상을 조정할 수 있다.
- 구분선 구현 borderBottomWidth: 0.5, borderBottomColor: '#eee', 와 같이 구현할 수 있다.
import React from "react";
import {
StyleSheet,
Text,
View,
Image,
TouchableOpacity,
ScrollView,
SafeAreaView,
} from "react-native";
const mainImg =
"https://storage.googleapis.com/sparta-image.appspot.com/lecture/main.png";
const cardImg =
"https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fpizza.png?alt=media&token=1a099927-d818-45d4-b48a-7906fd0d2ad3";
export default function App() {
return (
<SafeAreaView>
<ScrollView style={styles.container}>
{/* 타이틀 */}
<Text style={styles.title}>나만의 꿀팁</Text>
{/* 메인 이미지 */}
<Image source={{ uri: mainImg }} style={styles.mainImage} />
{/* 탭 버튼 목록 */}
<ScrollView
horizontal
style={styles.btnContainer}
indicatorStyle={"white"}
>
<TouchableOpacity style={styles.btn01}>
<Text style={styles.textStyle}>생활</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.btn02}>
<Text style={styles.textStyle}>재테크</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.btn03}>
<Text style={styles.textStyle}>반려견</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.btn04}>
<Text style={styles.textStyle}>꿀팁 찜</Text>
</TouchableOpacity>
</ScrollView>
{/* 카드 목록 */}
<View style={styles.card}>
<Image style={styles.cardImg} source={{ uri: cardImg }} />
<View style={styles.textContainer}>
<Text style={styles.cardTitle}>
먹다 남은 피자를 촉촉하게!
</Text>
<Text style={styles.cardDesc} numberOfLines={3}>
먹다 남은 피자는 수분이 날라가기 때문에 처음처럼
맛있게 먹을 수 없는데요. 이럴 경우 그릇에 물을 받아
전자레인지 안에서 1분 30초에서 2분 정도 함께
돌려주면 촉촉하게 먹을 수 있습니다. 물이 전자레인지
안에서 수증기를 일으키고, 피자에 촉촉함을
더해줍니다.
</Text>
<Text style={styles.cardDate}>2020.09.09</Text>
</View>
</View>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
backgroundColor: "#fff",
},
title: {
fontSize: 25,
marginTop: 20,
marginLeft: 20,
fontWeight: "bold",
},
mainImage: {
width: "90%",
height: 200,
borderRadius: 5,
alignSelf: "center",
margin: 10,
},
btnContainer: {
margin: 20,
height: 60,
},
btn01: {
backgroundColor: "rgb(246, 204, 113)",
width: 100,
borderRadius: 5,
justifyContent: "center",
alignItems: "center",
marginRight: 10,
},
btn02: {
backgroundColor: "rgb(241, 156, 131)",
width: 100,
borderRadius: 5,
justifyContent: "center",
alignItems: "center",
marginRight: 10,
},
btn03: {
backgroundColor: "rgb(178, 223, 208)",
width: 100,
borderRadius: 5,
justifyContent: "center",
alignItems: "center",
marginRight: 10,
},
btn04: {
backgroundColor: "rgb(238, 150, 180)",
width: 100,
borderRadius: 5,
justifyContent: "center",
alignItems: "center",
marginRight: 10,
},
textStyle: {
color: "#fff",
},
card: {
flex: 1,
flexDirection: "row",
marginHorizontal: 20,
paddingVertical: 15,
borderBottomWidth: 0.5,
borderBottomColor: "#eee",
},
cardImg: {
flex: 1,
width: "100%",
height: "100%",
borderRadius: 8,
marginRight: 10,
},
textContainer: {
flex: 2,
flexDirection: "column",
height: "100%",
},
cardTitle: {
fontSize: 22,
fontWeight: "bold",
},
cardDesc: {
fontSize: 18,
},
cardDate: {
fontSize: 16,
color: "gray",
},
});
모듈과 반복문
- 모듈 시스템
여러 자바스크립트 파일이 있을 경우, 서로 다른 자바스크립트 파일에서 각 파일에 있는 함수를 불러오거나, 자바스크립트 파일 자체를 불러올 때 사용한다.
//A.js 파일
//times, plusTwo 함수를 외부로 내보낼 준비를 합니다.
export function times(x) {
return x * x;
}
export function plusTwo(number) {
return number + 2;
}
//B.js 파일
//다른 자바스크립트 파일에서 다음과 같이 불러와 사용합니다.
import { times, plusTwo } from './util.js';
console.log(times(2));
console.log(plusTwo(3));
data.json 의 데이터를 가져와서 위에 구현했던 카드 목록에 붙여넣어보자.
data.json
{
"tip":[
{
"idx":0,
"category":"생활",
"title":"먹다 남은 피자를 촉촉하게!",
"image":"https://storage.googleapis.com/sparta-image.appspot.com/lecture/pizza.png",
"desc":"먹다 남은 피자는 수분이 날라가기 때문에 처음처럼 맛있게 먹을 수 없는데요. 이럴 경우 그릇에 물을 받아 전자레인지 안에서 1분 30초에서 2분 정도 함께 돌려주면 촉촉하게 먹을 수 있습니다. 물이 전자레인지 안에서 수증기를 일으키고, 피자에 촉촉함을 더해줍니다.",
"date":"2020.09.09"
},
{
"idx":1,
"category":"생활",
"title":"바나나를 싱싱하게 보관하기",
"image": "https://storage.googleapis.com/sparta-image.appspot.com/lecture/banana.png",
"desc":"바나나에 날파리가 꼬이거나 금방 익어버리는 것을 예방하기 위한 방법인데요. 바나나 양쪽 끝을 자른 후, 보관용 케이스나 비닐봉지에 묶어 밀봉합니다. 그리고 냉장고에 넣어주면 되는데요. 하루에 1~2개씩 꺼내서 싱싱하게 먹을 수 있습니다.",
"date":"2020.09.09"
},
{
"idx":2,
"category":"생활",
"title":"셔츠에 묻은 볼펜 자국 없애기",
"image": "https://storage.googleapis.com/sparta-image.appspot.com/lecture/shirt.png",
"desc":"셔츠를 자주 입는 사람의 경우, 종종 볼펜 자국이 묻기 마련인데요. 이럴 경우에는 집에 있는 물파스로 가볍게 지울 수 있습니다. 옷 뒷부분을 키친타올로 받쳐 번지는 것을 방지한 후, 볼펜 자국 있는 부분을 물파스로 눌러주고, 키친타올로 닦아냅니다.",
"date":"2020.09.09"
},
{
"idx":3,
"category":"재테크",
"title":"잠자는 내 돈을 찾아라",
"image": "https://storage.googleapis.com/sparta-image.appspot.com/lecture/money1.png",
"desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.",
"date":"2020.09.09"
},
{
"idx":4,
"category":"재테크",
"title":"할인행사, 한정할인판매 문구의 함정 탈출!",
"image": "https://storage.googleapis.com/sparta-image.appspot.com/lecture/money2.png",
"desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ",
"date":"2020.09.09"
},
{
"idx":5,
"category":"생활",
"title":"방전된 건전지 살리기",
"image": "https://storage.googleapis.com/sparta-image.appspot.com/lecture/battery.png",
"desc":"건전지를 다 사용하지 않아도 방전되면, 버리는 경우가 종종 있는데요. 건전지의 무게감이 느껴진다면, 드라이기를 활용해 방전된 건전지를 깨울 수 있습니다. 드라이기 열기를 10초~30초 정도 골고루 가해주면 되는데요. 건전지가 불필요하게 낭비되는 것을 막을 수 있습니다.",
"date":"2020.09.09"
},
{
"idx":6,
"category":"반려견",
"title":"반려견에게 배변 교육 시킬 때",
"image": "https://storage.googleapis.com/sparta-image.appspot.com/lecture/puppy.png",
"desc":"우선, 배변패드를 순서대로 돌며 간식을 조금씩 떨어뜨려 놓는다. 2단계는 배변패드 앞에서 기다렸다 반려견이 스스로 올라오면 간식을 주어서 보상하고, 3단계는 “화장실 가자”나 “매트” 같은 명령어를 붙여 말한 뒤 배변패드에 올라오면 간식을 주는 것이다. 마지막 단계는 배변패드에 올라간 반려견이 대소변을 본 다음 간식을 줌으로써 이 장소가 즐거운 곳이라는 인식을 심어주는 것이다. 그리고 무엇보다 1, 2회 사용한 배변패드는 바로 갈아줘야 한다.",
"date":"2020.09.09"
},
{
"idx":7,
"category":"반려견",
"title":"반려견이 주인과 떨어지는 것을 무서워 할 때",
"image": "https://storage.googleapis.com/sparta-image.appspot.com/lecture/puppy2.png",
"desc":"분리불안교육은 반려견에게 혼자 남는 법을 알려주기 위한 것이 아니라, 보호자가 ‘언제나 너에게 돌아올 거야’라고 알려주는 교육이다. 반려견과 5초 동안 떨어져 있다가 다시 문을 열고 들어가 손 냄새를 맡게 해주는 훈련을 하루 10번씩 7일 동안 반복하는 ‘5,10,7 법칙’을 통해 반려견의 마음을 편안하게 해줄 수 있다.",
"date":"2020.09.09"
},
{
"idx":8,
"category":"반려견",
"title":"반려견을 아이와 함께 키울 때",
"image": "https://storage.googleapis.com/sparta-image.appspot.com/lecture/puppy3.png",
"desc":"‘인간의 행복’을 위해 반려동물을 키우는 것에 대해 꾸준히 비판과 우려를 제기해온 그는 특히 ‘아이들의 정서’를 위해 반려견을 키우려 한다는 부모들에게 당부한다. “반려동물을 통해 아이들의 정서가 좋아진다면, 그것은 부모가 나와 생김새와 느낌, 말과 행동이 다른 동물을 아끼는 모습을 보기 때문입니다.” 인간의 뜻에 의해 인간과 함께 살게 된 생명을 좀 더 이해하고 행복하게 살 수 있도록 하는 것은 역시 인간의 노력에 달려 있다.",
"date":"2020.09.09"
},
{
"idx":9,
"category":"재테크",
"title":"렌탈 서비스 금액 비교해보기",
"image": "https://storage.googleapis.com/sparta-image.appspot.com/lecture/money2.png",
"desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ",
"date":"2020.09.09"
}
]
}
map을 이용하여 JSX 안에서 반복 적용
이 때 map 을 이용할 경우 반복되는 데이터에 고유한 키 값을 부여해주어야 오류가 나지 않는다.
import React from "react";
import {
StyleSheet,
Text,
View,
Image,
TouchableOpacity,
ScrollView,
SafeAreaView,
} from "react-native";
import data from "./data.json";
const mainImg =
"https://storage.googleapis.com/sparta-image.appspot.com/lecture/main.png";
const cardImg =
"https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fpizza.png?alt=media&token=1a099927-d818-45d4-b48a-7906fd0d2ad3";
export default function App() {
const tip = data.tip;
return (
<SafeAreaView>
<ScrollView style={styles.container}>
{/* 타이틀 */}
<Text style={styles.title}>나만의 꿀팁</Text>
{/* 메인 이미지 */}
<Image source={{ uri: mainImg }} style={styles.mainImage} />
{/* 탭 버튼 목록 */}
<ScrollView
horizontal
style={styles.btnContainer}
indicatorStyle={"white"}
>
<TouchableOpacity style={styles.btn01}>
<Text style={styles.textStyle}>생활</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.btn02}>
<Text style={styles.textStyle}>재테크</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.btn03}>
<Text style={styles.textStyle}>반려견</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.btn04}>
<Text style={styles.textStyle}>꿀팁 찜</Text>
</TouchableOpacity>
</ScrollView>
{/* 카드 목록 */}
{tip.map((content, i) => {
const { image, title, desc, date } = content;
return (
<View style={styles.card} key={i}>
<Image
style={styles.cardImg}
source={{ uri: image }}
/>
<View style={styles.textContainer}>
<Text style={styles.cardTitle}>{title}</Text>
<Text style={styles.cardDesc} numberOfLines={3}>
{desc}
</Text>
<Text style={styles.cardDate}>{date}</Text>
</View>
</View>
);
})}
{/* <View style={styles.card}>
<Image style={styles.cardImg} source={{ uri: cardImg }} />
<View style={styles.textContainer}>
<Text style={styles.cardTitle}>
먹다 남은 피자를 촉촉하게!
</Text>
<Text style={styles.cardDesc} numberOfLines={3}>
먹다 남은 피자는 수분이 날라가기 때문에 처음처럼
맛있게 먹을 수 없는데요. 이럴 경우 그릇에 물을 받아
전자레인지 안에서 1분 30초에서 2분 정도 함께
돌려주면 촉촉하게 먹을 수 있습니다. 물이 전자레인지
안에서 수증기를 일으키고, 피자에 촉촉함을
더해줍니다.
</Text>
<Text style={styles.cardDate}>2020.09.09</Text>
</View>
</View> */}
{/* 카드 목록 끝 */}
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
backgroundColor: "#fff",
},
title: {
fontSize: 25,
marginTop: 20,
marginLeft: 20,
fontWeight: "bold",
},
mainImage: {
width: "90%",
height: 200,
borderRadius: 5,
alignSelf: "center",
margin: 10,
},
btnContainer: {
margin: 20,
height: 60,
},
btn01: {
backgroundColor: "rgb(246, 204, 113)",
width: 100,
borderRadius: 5,
justifyContent: "center",
alignItems: "center",
marginRight: 10,
},
btn02: {
backgroundColor: "rgb(241, 156, 131)",
width: 100,
borderRadius: 5,
justifyContent: "center",
alignItems: "center",
marginRight: 10,
},
btn03: {
backgroundColor: "rgb(178, 223, 208)",
width: 100,
borderRadius: 5,
justifyContent: "center",
alignItems: "center",
marginRight: 10,
},
btn04: {
backgroundColor: "rgb(238, 150, 180)",
width: 100,
borderRadius: 5,
justifyContent: "center",
alignItems: "center",
marginRight: 10,
},
textStyle: {
color: "#fff",
},
card: {
flex: 1,
flexDirection: "row",
marginHorizontal: 20,
paddingVertical: 15,
borderBottomWidth: 0.5,
borderBottomColor: "#eee",
},
cardImg: {
flex: 1,
width: "100%",
height: "100%",
borderRadius: 8,
marginRight: 10,
},
textContainer: {
flex: 2,
flexDirection: "column",
height: "100%",
},
cardTitle: {
fontSize: 22,
fontWeight: "bold",
},
cardDesc: {
fontSize: 18,
},
cardDate: {
fontSize: 16,
color: "gray",
},
});
{} 표현식과 조건문
화면을 표현하다보면 조건에 다라 다른 모습을 보이거나, 연산을 진행하길 원할 수 있다.
이 때 조건문을 사용해야 하는데, 삼항 연산자를 사용해서 간단하게 구현해보자.
위와 같이 홀수번째 카드에만 배경색을 바꾸고 싶다면 key값으로 넘겨준 홀수번째 인덱스 마다 배경값을 바꿔주면 될 것이다.
홀수의 유무는 2 로 나누어서 나머지가 0 이냐 아니느냐에 따라서 짝수/홀수가 나뉠 것이다.
<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>
그리고 짝수/홀수에 따라서 스타일을 다르게 부여하면 된다!
const styles = StyleSheet.create({
...
cardEven:{
flex:1,
flexDirection:"row",
margin:10,
backgroundColor:"#FFFED7",
borderRadius:20,
borderBottomWidth:0.5,
borderBottomColor:"#eee",
paddingBottom:10
},
cardOdd:{
flex:1,
flexDirection:"row",
margin:10,
borderBottomWidth:0.5,
borderBottomColor:"#eee",
paddingBottom:10
},
});