상세 컨텐츠

본문 제목

앱 3주차

취미/앱

by Kyzen 2021. 12. 23. 13:53

본문

01. 오늘 배울 것

  • 오늘 배울 내용이 두 가지는 앞으로도 계속~ 반복 숙달합니다 🙂
    1. 리액트 필수 지식 활용
    2. Expo 기능 사용
    3. 페이지 적용
    </aside>
  • 오늘은 앱다운 앱을 만드는 연습과 기술을 배울텐데요! 다음 순서대로 지금까지 배웠던 것을 복습 하면서 앱다운 앱 기능을 배우니 점점 더 여러분은 앱 개발에 숙달되는 모습을 볼 수 있습니다!
  • <aside> 👉 지난 시간까지 자바스크립트 기초체력을 다지고 JSX로 화면을 그리는 방법 까지 배워봤습니다.
  • [앱 필수 기초지식] 리액트 필수지식
    • 컴포넌트(Component)
    • 상태(State,useState)
    • 속성(Props)
    • useEffect
    이렇게 네 가지입니다. 단어가 조금 어려워 보이지만, 재미있고 간단한 개념들입니다. 이 개념들을 통해 앱 코드를 체계적으로 관리할 수 있고 화면안에서 데이터를 자유자재로 관리할 수도 있습니다.리액트 네이티브(React Native)는 리액트(React.js) 기반으로 만들어진 앱 개발 기술이기 때문이에요! 겁먹지마시고 차근차근 다루어봅시다.
  • </aside>
  • 최소한의 리액트 개념을 배우는 이유는
  • <aside> 👉 리액트 네이티브 앱을 만들기 위해서 알아야 하는 최소한의 리액트 개념을 배웁니다.
  • [Expo 앱다운 앱기능] 앱다운 앱을 위해 사용할 리액트 네이티브와 Expo 기능들이렇게 앱 다운 앱이 되기위한 기능들을 공식문서를 보고 하나하나 적용해보는 시간을 갖습니다. 이 연습을 거치면 여러분들은 공식문서를 보며 필요한 앱 기능들을 여러분들 앱에 붙여나갈 수 있습니다!
  • </aside>
  • <aside> 👉 앱이라고 하면 보통 현재 보고 있는 내용을 친구들한테 공유하거나, 앱 안에 있는 링크를 누르면 바로 해당 링크로 화면이 전환되는 기능들이 들어 있습니다. 또한 배터리를 볼 수 있는 상태바 스타일도 앱 전체적인 스타일에 맞게 변경할 수도 있어야 할겁니다.
  • [앱 페이지 적용] 앱에 페이지 기능 넣기!그래서 앱안에 여러 페이지를 두고 버튼을 눌러 이동할 수 있는 기능을 달아봅니다. 본격적으로 거의 온전한 앱의 모습을 띄게 됩니다!
  • </aside>
  • <aside> 👉 앱에 페이지가 하나라면 너무 단조롭고 볼게 없는 앱이 되겠죠?

02. [함께해보기] 나만의 꿀팁 앱 상세 화면

  • [실습 ✍️ ] 나만의 꿀팁 상세 페이지가될 아래의 화면을 만들어보세요
  • 퀴즈 가이드
    • [현재 코드 상황]
      • App.js
      • import React from 'react' import MainPage from './pages/MainPage'; import AboutPage from './pages/AboutPage'; export default function App(){ // return (<MainPage/>) return (<AboutPage/>) }
      • MainPage.js
      • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { 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> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, 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", } });
      • AboutPage.js
      • import React from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' export default function AboutPage(){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:100, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
      • 폴더구조
    • DetailPage.js를 pages 폴더에 만들어 넣기</aside>
    • <aside> 👉 저번 1주차 마무리 시간에, pages 폴더를 만들고 AboutPage.js 파일을 만들어 넣었던 기억이 나시나요? 그때랑 동일하게 pages 폴더안에 DetailPage.js 파일을 만들고 App.js에서 return 부분에 DetailPage.js를 넣어주세요 다음과 같이요!
    • 문제 디테일 화면을 위한 데이터딕셔너리 형태로 키값에 접근해서 값을 꺼내 사용해도 되고! 문자열들 그대로 복사해서 Text 태그에 넣어 사용해도 됩니다!
      • [코드스니펫] 상세 화면에서 사용될 데이터
      • const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }
    • </aside>
    • <aside> 👉 화면에 나타낼 꿀 팁은 다음 데이터를 사용하세요.
  • 함께 해보기</aside>
    • [코드스니펫] DetailPage.js 화면
    • import React from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage() { const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" } const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
  • <aside> 👉 DetailPage를 확인하려면 App.js를 좀 바꿔야 겠죠? 어떻게 바꿔야 할까요?

03**.** [앱 필수 기초지식] 컴포넌트, 속성, 상태, useEffect

  • 공부 할 리액트 필수 지식<aside> 👉 다시 떠올리자면, 리액트 네이티브는 리액트(React.js) 라이브러리 기반으로 만들어진 프레임워크 입니다. 그렇기 때문에 기본적인 구조는 리액트를 닯아 있습니다.1) 컴포넌트(Component) : 정해진 엘리먼트들(요소)을 사용하여 만든 화면의 일부분 2) 상태(State) : 컴포넌트에서 데이터를 유지하고 관리하기 위한 유일한 방법 == 그냥 사용할 데이터! 3) 속성(Props) : 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하는 방식 == 그냥 데이터 전달! 4) useEffect : 화면에 컴포넌트가 그려지면 처음 실행해야 하는 함수들을 모아두는 곳</aside>
  • 네 가지가 있습니다. 워워 어렵지 않아요. 이것도 정해진 규칙과 개념일 뿐! 사용방법 숙지!만 하면 그만이랍니다!
  • 대표적으로
  • 컴포넌트(Component)</aside><aside> 👉 컴포넌트는 즉, 화면의 모든 부분입니다. 그리고 컴포넌트란 App.js의 큰 App 함수처럼, 코드 전체를 감싸고 있는 함수를 뜻하기도 합니다.</aside>이렇게 컴포넌트로 생각하고 앱을 개발해 나아간다면, 코드 재사용이 용이 해집니다.실제 우리 예제에 어떻게 적용할 수 있는지. 확인해보겠습니다.
  • </aside>
  • 코드 재사용이란 어려운 용어는 아니고, 버튼 하나를 만들었을 때 이 버튼 코드를 여러 페이지에서 사용 가능하다는 뜻입니다.
  • <aside> 👉 버튼 하나가 컴포넌트가 될 수 있고, 버튼을 모아둔 영역이 컴포넌트가 될 수 있습니다.
  • 따라서 App.js를 App 컴포넌트라고도 부를 수 있습니다.
  • <aside> 👉 컴포넌트는 UI의 요소, 요소를 재사용 가능한 부분으로 조각내서 운영하는 기법입니다. 리액트 기반으로 만들어진 페이스북 웹사이트는 운영되는 컴포넌트가 수 만가지라고 합니다
  • [실습 ✍️ ] 메인화면 컴포넌트화 해보기그럼 다음 코드를 Card.js에 넣어주세요! 어디서 많이 본 코드입니다 그쵸?<aside> 🚧 마찬가지로 MainPage.js 화면을 확인하려면 App.js를 바꿔야 겠죠? return (<MainPage/>) 으로요!
    • [코드스니펫] Card.js
    • import React from "react" import {View,Text,Image,StyleSheet} from "react-native"; //비구조 할당 방식으로 넘긴 속성 데이터를 꺼내 사용함 export default function Card({content}) { return (<View style={styles.card}> <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>) } const styles = StyleSheet.create({ 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", } })
    <aside> 👉 맞습니다! MainPage.js에서 이미지위에 텍스트가 있던, 한 줄에 세 개씩 나열 되었던 그 카드 버튼입니다!.</aside>
    • [코드스니펫] MainPage.js
    • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; **import Card from '../components/Card';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (**<Card content={content} key={i}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
    <aside> 👉 MainPage.js에 있던 코드들을 Card.js로 옮겼습니다. 이렇게 옮기게 되면, 추후에 MainPage.js가 아닌 다른 페이지(페이지 또한 컴포넌트로 볼 수 있습니다) 또는 컴포넌트에서 Card.js를 사용할 수 있습니다.</aside>
    • MainPage.js
    • Card.js
    <aside> 👉 미리 말씀드리자면 MainPage.js에서 Card.js로 데이터를 넘기고 있는 모습입니다! 이를 속성을 넘긴다! 라고 보통 일컫는데 이는 다음 챕터에서 더 자세히 다루도록 하겠습니다!
  • </aside>
  • 그런데 약간 생소한 부분이 있습니다 다음 두 부분인데요!
  • 그럼 MainPage.js는 다음과 같이 변경해주세요
  • </aside>
  • </aside>
  • <aside> 👉 Components 폴더 안에 Card.js라는 파일을 만듭니다. 보통 컴포넌트들은 components 폴더에 모아두면서 관리하곤 합니다.
  • 속성(Props)<Text> 태그엔 numberOfLines이란 속성이 있었습니다. 말줌임표 효과를 주었죠! <Image> 태그엔 resizeMode란 속성이 있었습니다. 이미지가 영역을 차지하는 방식을 나타냈죠.</aside>예를 들어 Card를 사용하는 곳에서<Card image={'이미지 주소'}/> 라고 속성을 써 넣으면, 이 <Card/>는 image라는 속성이름에 {'이미지 주소'} 값을 을 갖게 됩니다. 즉 키와 벨류의 값을 갖게 됩니다 근데 이게 무슨 의미가 있을까요?</aside>
  • 의미가 있습니다. 속성을 부여받은 컴포넌트에서 해당 속성 값을 받아서 사용 할 수 있거든요! 다음과 같이 말이죠!
  • <aside> 👉 우리가 만든 <Card/> 컴포넌트에도 속성이 존재합니다. 우리가 부여 하면 부여하는 대로 속성이 되는 겁니다.
  • 이 모든 속성들은 공식 문서에 나와 있고, 해당 엘리먼트들이 태어날 때부터 가지고 있는 속성이였습니다.
  • <aside> 👉 속성은 쉽게 생각해서 컴포넌트에 데이터를 전달한다는 것이다. 그 전달 모습은 키와 벨류의 형태입니다.
  • [확인해보기👀 ]</aside>
    • MainPage.js 에서의 Card.js 컴포넌트
    <aside> 👉 어렵지 않은 규칙이 숨어 있습니다!
    1. 컴포넌트에 속성(데이터)을 부여해줘서 전달할땐, 키와 벨류(content={content}) 형태로 전달해줘야 할 것
    2. 컴포넌트를 반복문 돌릴땐, 컴포넌트마다 고유하다는 것을 표현하기 위해, map에서 나오는 인덱스(i)를 key = {i} 속성 전달 형태로 꼭 넣을것! 입니다.
    </aside>
    • Card.js 에서의 속성 값 내려 받기
    <aside> 👉 오랜만에 복습겸 나온 비구조 할당 방식 의 모습입니다. MainPage.js에서 넘겨준 속성은 실제 받게되는 컴포넌트에서 정말 딕셔너리 데이터를 받았다! 라고 생각하면 됩니다.이 방식으로 넘겨준 키값을 {키,키,키} 비구조 할당 방식으로 바로 꺼내서 사용하면 됩니다!
  • </aside>
  • 때문에, 비구조 할당 방식이 뭐였죠? 딕셔너리에서 키값을 바로 취해서 변수로써 함수안에서 즉시 사용할 수 있는 방식이였죠?
  • <aside> 👉 MainPage.js에서 방금전 컴포넌트화! 했던 Card.js를 우린 이렇게 사용했습니다.
  • 상태(State, useState)<aside> 👉 컴포넌트마다 데이터를 보유하고 관리 할 수 있습니다. 데이터라고 불러도 되지만, 리액트에서는 컴포넌트에서 보유/관리 되는 데이터를 **상태**라 부릅니다.사용방법은 실습을 통해 알아보도록 하겠습니다!
  • </aside>
  • 리액트에서 상태(state)는 리액트 라이브러리에서 제공해주는 useState로 생성하고 setState 함수로 정/변경 할 수 있습니다.
  • useState, 그렇다면 왜 중요할까?여러분이 만드는 화면은 데이터에 따라 변경됩니다 또 아무 데이터가 아니라 이 상태(state)로 관리되는 데이터가 변경되면 화면이 바뀝니다.</aside><aside> 👉 당시 개발자들끼리 이 상태와 컴포넌트의 관계를 공식으로 표현한 글을 보고 대단하다!! 했던 기억이 있는데, 여러분들은 어떻게 느끼실지 궁금하네요 ㅎㅎ무슨말인지 아직 잘 와닿지 않으시죠? 다른건 잘 와닿지 않아도 state는 컴포넌트에서 관리되는 상태만 기억하시면 충분합니다 이 개념은 이번 강의에서 개념을 다 배운 뒤, 다음 6 강에서 실습 하며 직접 확인할 수 있습니다
  • </aside>
  • 저 공식은 즉, 사용자 화면(UI)는 컴포넌트(component)에 어떤 데이터(state)가 주입되고 변경되냐에 따라 변화된다를 뜻합니다
  • UI = component(state)
  • 모르셔도 되지만 꽤 유의미한 공식이 있습니다
  • <aside> 👉 리액트는 특이한 점이 있습니다.
  • 화면이 그려진다음 가장 먼저 실행되는 함수, useEffect화면이 그려진 다음 가장 먼저 실행되는 함수로 사용방법이 간단합니다 형식만 알고 있으면 충분합니다
    **useEffect(()=>{**
    
    	...화면이 그려진 다음 가장 먼저 실행되야 할 코드 작성 공간
    
    **},[])**
    
    <aside> 👉 useEffect(()=>{ },[])</aside>
  • 이 안에서, 화면이 그려진다음 실행시키고 싶은 함수를 작성한다면 가장먼저 실행이 됩니다. useEffect 또한 어떤 역할을 하는지만 알고! 바로 실습을 통해 확인해보도록 하겠습니다
  • </aside>
  • <aside> 👉 useEffect는 너무 간단하고 유용하게 쓰이는 리액트 기본 제공 함수입니다.
  • useEffect, 그렇다면 왜 중요할까?
    1. 화면이 그려진다
    2. useEffect가 데이터를 준비한다
    3. 상태 데이터가 업데이트 되었으니 화면이 다시 그려진다
    화면이 그려진 다음, 서버에게 필요한 데이터를 요청하여 받은 후, 화면을 다시그릴때 주로 사용되는데요! 아주 일반적인 패턴이므로 여러분들도 금방 이해하고 익숙해지게 됩니다!
  • </aside>
  • <aside> 👉 보통 useEffect는 데이터를 준비할 때 사용합니다 데이터를 준비한다는 것은, 데이터를 서버로부터 혹은 어디선가로부터 받은 후 상태(state)에 반영한다는 것을 뜻합니다. 이런순서로 말이죠
  • [실습✍️ ] data.json을 상태(state)에 넣어서 관리해보기복습하실때 주석도 자세히 살펴보세요!
    • [코드스니펫] MainPage.js
    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //useState 사용법 //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수 //setState는 state를 변경시킬때 사용해야하는 함수 //모두 다 useState가 선물해줌 //useState()안에 전달되는 값은 state 초기값 const [state,setState] = useState([]) //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수 //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음 useEffect(()=>{ setState(data) },[]) // let tip = data.tip; //data.json 데이터는 state에 담기므로 상태에서 꺼내옴 let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
    <aside> 👉 그러나 실행을 하면 다음과 같은 오류 화면이 나옵니다
    • 오류화면
    <aside> 👉 그 이유는 이런 절차로 발생됩니다.
    1. 화면이 그려진다
    2. useEffect가 데이터를 state에 useState를 이용하여 업데이트한다
    3. 상태(state)가 변경되었으니 화면이 다시 그려진다
    여기서 1번에서 오류가 난겁니다. 화면이 가장 먼저 그려질때, 우리가 Card에 데이터를 넘기기위해 어떤일을 했나요?이런 오류는 우리가 지금 배운 개념들, 상태, 컴포넌트, useEffect를 이용하여 로딩화면을 만들어줌으로써 해결이 가능합니다!
  • </aside>
  • 처음부터 데이터가 없어서 , 꺼내올수 없는 오류
  • <aside> 👉 상태(state)에서 tip 키 값에 접근하여 데이터를 꺼내 map 즉, 반복문을 돌리려했습니다. 그런데 state에 데이터가 처음부터 있었나요? 없었죠? useEffect를 거쳐야 state에 데이터가 담기는데 아직 없는 상태라 오류가 나는 겁니다.
  • </aside>
  • </aside>
  • </aside>
  • <aside> 👉 지금 꿀팁을 data.json 파일에서 꺼내 바로 JSX 문법에서 사용하고 있습니다. 이를 다음과 같이 상태에 저장한 다음 사용하도록 변경해보도록 하겠습니다.

04. [앱 필수 기초지식 응용] 로딩화면, 카테고리 기능, 상태 바

  • 앱 운영에 필요한 로딩화면이때 우린 로딩화면을 만들어줌으로써 쉽게 해결이 가능합니다. 컴포넌트 폴더에 Loading.js 파일을 만들어주고 다음 코드를 넣어주세요
    • [코드스니펫] Loading.js
    • import React from 'react'; import {View,Text,StyleSheet} from 'react-native'; export default function Loading(){ return(<View style={styles.container}><Text style={styles.title}>준비중입니다...</Text></View>) } const styles = StyleSheet.create({ container: { //앱의 배경 색 flex:1, justifyContent:'center', alignItems:'center', backgroundColor: '#fdc453', }, title: { fontSize:20, fontWeight:'700' } })
    <aside> 👉 그리고 MainPage.js 다음 코드 스니펫을 넣어주세요
    • [코드스니펫] MainPage.js
    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 const [state,setState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. **const [ready,setReady] = useState(true)** useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 **setTimeout(()=>{ setState(data) setReady(false) },1000)** },[]) // let tip = data.tip; let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" **//처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐)** return **ready ? <Loading/>** : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
    • 적용 모습
    <aside> 👉 ready 라는 새로운 상태값이 추가되었습니다. 즉, 상태값은 컴포넌트안에 여러개가 될 수 있습니다.즉, 이런 순서로 진행된거에요!
    1. ready 값이 true이므로 return 구문에서 ? 물음표 바로 뒤의 Loading 컴포넌트가 화면에 그려짐
    2. 화면이 그려지고 난다음, 1초 이따가 상태값들이 채워지고 변경됨
    3. ready 상태 값이 false가 됨
    4. 상태값이 변경되었으므로 화면이 다시 그려짐
    5. ready 값이 false 이므로 return 구문에서 : 콜론 뒤의 MainPage 컴포넌트가 화면에 그려짐
    차근차근 순서따라 반복해서 보면 별거아닙니다. 정해진 규칙에 따라 우린 코드를 적용했고 정해진 규칙에 따라 코드가 실행되고 있는 거에요!
  • </aside>
  • useEffect안에 setTimeout이라는 함수가 존재합니다. , 뒤에 있는 숫자 (1000 === 1초) 만큼, 지연됐다가 안에 있는 코드가 실행되는 지연 함수입니다.
  • </aside>
  • </aside>
  • <aside> 👉 5장에서 오류가 났던 것 처럼 화면이 그려질때 준비된 데이터가 없다!? 이러한 상황은 아주 빈번합니다.
  • 카테고리 버튼상태값이 바뀌면 화면이 다시 그려진다! 라는 개념을 염두하고 한번 만들어보겠습니다!
  • </aside>
  • <aside> 👉 우리에게 이런 버튼이 있었습니다. 카테고리 버튼인데요! 이 버튼은 장식이아니라, 실제 누르면 하단의 데이터가 카테고리에 맞게 다시 정렬되는 기능이 의도되어 있습니다.
  • [실습✍️ ] 카테고리 기능 넣기그리곤 함수가 필요합니다. 우리가 만든, 생활 재테크 반려견 버튼에 연결할 함수인데요!? 이 함수는 카테고리에 따라 카테고리 상태데이터를 새롭게 구성해주는 기능을 합니다.
    • [코드스니펫] MainPage.js
    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 **const [state,setState] = useState([])** //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 **const [cateState,setCateState] = useState([])** //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 **let tip = data.tip; setState(tip) setCateState(tip) setReady(false)** },1000) },[]) **const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } }** let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> **<TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>** <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, **middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 },** 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" }, **middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" },** cardContainer: { marginTop:10, marginLeft:10 }, });
    • 제태크를 눌렀을때의 모습
  • </aside>
  • <aside> 👉 카테고리 기능을 위해선 먼저 카테고리 상태가 필요합니다. 카테고리에 맞는 꿀팁들을 지속적으로 저장하고 관리할 상태 그릇이죠!
  • Expo에서 제공해주는 앱다운 앱 기능들</aside>
  • <aside> 💡 Expo SDK. 일명 Expo에서 제공해주는 앱 기능 도구들을 개발할 때 여기서 확인 할 수 있습니다. (링크) 필요한 기능들이 있는지 목록을 쭉 보고, 원하는 기능을 선택해서 적용하면 끝!
  • 상태 바란?</aside><aside> 💡 배터리가 충분한지, 몇시인지 매일 보시죠? 이부분도 우리가 처리를 할 수 있습니다.
  • </aside>
  • <aside> 💡 앱이 앱에 따라 모바일 맨 위 상태 바가 변하는 앱이 있습니다. 상태바란 이런 겁니다. 여러분들 매일 같이 보는 부분이에요!
  • StatusBar
    • 본격적으로 라이브러리 설치 시작!</aside><aside> 👉 터미널 우측 버튼중에 분할이란 버튼이 있습니다.</aside>
    • 여러분들 터미널 왼편은 서버를 켜고 끄고를 담당하고(expo start) 터미널 우측은 필요한 라이브러리들을 설치할 때 명령어를 치는 장소로 분할해서 사용하면 편리합니다!
    • <aside> 👉 이제 본격적으로 라이브러리들을 설치합니다. vscode에서 터미널을 이렇게 변경해주세요
    • [코드스니펫] Expo 상태 바 설치
    • expo install expo-status-bar
    • [실습 ✍️] 적용적용 모습을 살펴볼까요?
      • [코드스니펫] MainPage에 StatusBar 설치
      • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; **import { StatusBar } from 'expo-status-bar';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> **<StatusBar style="black" />** <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
      <aside> 💡 style이 light일 때와, black일 때가 다릅니다. 우린 앱 화면 배경색을 검은색으로 설정했기 떄문에, 상태바를 black으로 하면 보이지 않습니다.Status Bar 공식문서
      • black 일때
      • light 일때
    • </aside>
    • 상태 바 속성은 공식문서에 다양하게 존재하니, 살펴보면서 앱에 적합한 상태 바를 적용해보세요!
    • </aside>
    • <aside> 💡 컴포넌트마다 다르게 적용할 수도 있고, 앱 전체에 공통적으로 적용할 수 있습니다. StatusBar를 컴포넌트 각각 다르게 둘 수 있습니다 우린 MainPage.js에 달아볼거에요!

05**. [앱** 페이지 적용**] 네비게이터 사용하기**

  • 네비게이션이란?<aside> 👉 앱에 페이지 개념을 입혀주고! 웹 사이트를 이용하듯, 앱에서 여러분들이 만든 컴포넌트들을 페이지화 시켜주고, 해당 페이지끼리 이동을 가능하게 해주는 라이브러리입니다가령 다음 화면과 같이, 앱 하단에 탭 버튼을 두고 해당 버튼을 누르면 연결되어 있는 페이지로 바로바로 이동할 수 있게 해주는 기능(열 앱에서 많이 보셨죠?)도 가지고 있습니다.</aside>
  • 따라서 이번시간에 같이 공부한 다음 틈틈히 다른 기능들도 적용해보세요!
  • 이번 강의에선 앱 내의 페이지 구성 및 이동까지 직접 적용해볼 텐데요!, 이 라이브러리는 다양한 기능들을 가지고 있습니다.
  • <aside> 💡 결국 이 라이브러리도 Expo에서 지원하고 있는 도구로써, 앱을 만들기 위해 사용 중인 Expo와 궁합이 아주 잘 맞습니다.
  • </aside>
  • <aside> 💡 물론 이것 또한 쉽게 페이징을 도와주는 외부 라이브러리를 가져다가 사용합니다. react-navigation 공식문서 보러가기 —> (링크)
  • </aside>
  • 현재 갖추고 있는 페이지 구성 확인!</aside>
  • <aside> 💡 페이지 구성은 보통 pages 폴더 안에 생성해둔 컴포넌트로 구성합니다. 우리는 이미, MainPage.js 메인 페이지 DetailPage.js 상세 화면 페이지 그리고 숙제로 만든 소개화면인 AboutPage.js 까지 총 3 페이지를 가지고 있습니다.
  • 기본 설치 코드우리에게 필요한 것이라고 했지만, 거의 대부분의 앱에 적용이 되어 있는 것들만 가져왔습니다.아래 명령어를 차례차례 터미널에 넣어 실행해주세요 가장 마지막줄은 꽤 깁니다. 이렇게 여러 라이브러리들을 띄어쓰기로 연결해서 한번에 설치 할 수도 있습니다.
    • [코드스니펫] 네비게이션 설치 코드
    • yarn add @react-navigation/native
    • [코드스니펫] 네비게이션 추가 설치코드
    • expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
  • </aside>
  • 아래 명령어들은 네비게이션을 사용하기 위해 필요한 기본 라이브러리들이구요! 우리가 곧 배울 스택 네비게이션와 탭 네비게이션 기능은 추가적으로 라이브러리를 또 설치 해줘야 합니다 😂
  • <aside> 💡 리액트 네이티브에서 페이지 네비게이션을 구현하기란 조금 까다롭습니다. 그래서 딱 우리에게 필요한 것만 가져와 설치 하고, 적용해 나갈 계획입니다.
  • 스택 네비게이션이란?</aside><aside> 💡 컴포넌트를 페이지화 시키는 스택 네비게이션은 다음과 같습니다.이렇게 만든 여러 페이지들을 책갈피 기능을 하는 스택 네비게이터에 모조리 등록시켜서, 언제든지 이 페이지 이동이 가능하게끔 해줍니다</aside>
  • 페이지는 Stack.Screen 이라 부르며 책갈피는 Stack.Navigator라 부릅니다
  • 우리가 페이지처럼 만든 컴포넌트를, 정말 페이지처럼 사용할 수 있게끔 페이지로 컴포넌트를 감싸 페이지로 만들어줍니다.
  • <aside> 💡 스택 네비게이션은 컴포넌트에 페이지 기능을 부여해주고 컴포넌트에서 컴포넌트로 이동, 즉 페이지 이동을 가능하게 해줍니다
  • createStackNavigator 사용해보기
    • [코드스니펫] 스택 네비게이터 설치 코드
    • yarn add @react-navigation/stack
    • [실습 ✍️] 적용하기</aside>
      • [코드스니펫] StackNavigator.js 스택 네비게이터
      • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
      <aside> 👉 워워 진정하세요! 위에서부터 차근차근 살펴볼거에요 🙂 언제나 우린 미니카 조립 설명서를 보면서 조립하듯, 공식 문서 내용 그대로 적용한다는 것 꼭 잊지마시고! 살펴보도록 하겠습니다.
      • 💻스택 네비게이터 코드 분석
        • 공식 문서 링크
        • 적용 순서 1) 사용 준비
        • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator();
        • 적용 순서 2) 기본 틀
        • //리액트의 모~든 파일은 컴포넌트라 생각하고 //페이지 기능을 해주는 모든 기능이 담겨 있는 컴포넌트를 만든다 생각하세요! const StackNavigator = () =>{ return ( /// 페이지 기능이 들어갈 곳 ) } export default StackNavigator;
        • 적용 순서 3) 스크린 옵션
        • //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
        • 적용 순서 4) 페이지 연결
        • {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/>
      <aside> 👉 컴포넌트를 페이지화 했고, 페이지를 이동할 수 있는 네이게이션도 준비가 됐다면, 우리는 최상단 컴포넌트 즉 App.js에 네비게이션 기능을 달아야 합니다.</aside>
      • [코드스니펫] App.js
      • import React from 'react'; //이제 모든 페이지 컴포넌트들이 끼워져있는 책갈피를 메인에 둘예정이므로 //컴포넌트를 더이상 불러오지 않아도 됩니다. // import MainPage from './pages/MainPage'; // import DetailPage from './pages/DetailPage'; import { StatusBar } from 'expo-status-bar'; //메인에 세팅할 네비게이션 도구들을 가져옵니다. import {NavigationContainer} from '@react-navigation/native'; import StackNavigator from './navigation/StackNavigator' export default function App() { console.disableYellowBox = true; return ( <NavigationContainer> <StatusBar style="black" /> <StackNavigator/> </NavigationContainer>); }
      • 스택 네비게이터 적용 후 MainPage 모습
    • 즉, 앱 가장 최상위 코드에 네비게이션을 다는겁니다 그래야 앱 어디서든 원하는 페이지 이동이 가능할테니까요!
    • </aside>
    • <aside> 👉 navigation 폴더 하나를 만들고 StackNavigator.js 파일을 만들어주세요 그리고 StackNavigator 안에 다음 코드를 넣어주세요
  • 페이지 헤더 수정</aside><aside> 💡 그 이유는 StackNavigator에서 현재 헤더에 대한 스타일과 페이지에 헤더의 제목까지 결정해주고 있기 때문입니다.
    • 스택 네비게이터의 헤더 스타일 부분 코드
    • <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTintColor: "#fff", headerBackTitleVisible: false }} > {/* name에 해당 하는 부분이 페이지의 타이틀이 됩니다.*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
    <aside> 💡 따라서 헤더 스타일을 적절히 바꾸기 위해 다음과 같이 옵션 부분의 코드를 수정해보도록 하겠습니다.
    • [코드스니펫] StackNavigator.js
    • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
    • [코드스니펫] MainPage.js 에서 title 삭제!
    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
    • StackNavigator 옵션 코드 수정 후 모습
    <aside> 💡 그래도 위에 페이지 제목으로 MainPage가 되는 모습은 뭔가 또 수정이 필요해보입니다. 이를 위해선 다음 스택 네비게이션 페이지 이동 강의에서 살펴보도록 하겠습니다
  • </aside>
  • </aside>
  • </aside>
  • <aside> 💡 그런데 현재 MainPage 화면은 다음과 같이 상단의 모습이 약간 이상해 보입니다.
  • 페이지 이동하기일단 페이지를 이동시키려면, 책갈피가 페이지들에게 부여해준 페이지 이동 기능을 사용해야 합니다.</aside><aside> 💡 일단 먼저 구조를 보여드렸습니다. 물론 사용을 해봐야 감이 오겠죠?같이 해보죠!
    • [실습 ✍️] 데이터 없이 페이지 이동하기
      • [코드스니펫] MainPage.js
      • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (**<Card content={content} key={i} navigation={navigation}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
      • [코드스니펫] Card.js
      • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 **export default function Card({content,navigation}){** return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage')}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
    • navigation.navigate("DetailPage")
    • [실습 ✍️] 데이터 가지고 페이지 이동하기</aside><aside> 💡 그럼 Card에서 DetailPage로 이동할 때, MainPage로 부터 넘겨받은 content도 넘겨볼까요?</aside>
      • [코드스니펫] Card.js
      • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function Card({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage',**content**)}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
      • [코드스니펫] DetailPage.js
      • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage({navigation,route}) { //초기 컴포넌트의 상태값을 설정 //state, setState 뿐 아니라 이름을 마음대로 지정할 수 있음! const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) //Card.js에서 navigation.navigate 함수를 쓸때 두번째 인자로 content를 넘겨줬죠? //content는 딕셔너리 그 자체였으므로 route.params에 고대~로 남겨옵니다. //즉, route.params 는 content죠! navigation.setOptions({ //setOptions로 페이지 타이틀도 지정 가능하고 title:route.params.title, //StackNavigator에서 작성했던 옵션을 다시 수정할 수도 있습니다. headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
      • 이동한 디테일 페이지 모습
      <aside> 💡 건네 받은 값을 꺼낼 땐, 다음과 같이 책갈피가 navigation과 추가적으로 건네준 route에서 꺼내 확인 할 수 있습니다.</aside>
      • route에 담겨져 오는 데이터 콘솔에서 직접 확인
      • DetailPage에서 상태값을 초기에 설정한 이유</aside><aside> 💡 그 이유는 컴포넌트가 화면에 그려지는 순서에 있습니다. 이건 지금 단계에선 조금 과한감이 있어서 간략히만 설명해보면 다음과 같습니다.
        1. DetailPage 컴포넌트가 useState에 들어 있는 
           데이터 가지고 화면에 그려짐(return 함수실행)
        2. 화면에 다 그려진후, useEffect 함수 실행
        3. useEffect에서 상태값 변경 이벤트가 실행되면 변경된 데이터 가지고 다시 return 실행
        4. 변경된 데이터를 가지고 화면에 DetailPage가 다시 그려짐.
        
        <aside> 💡 곰곰히 생각해보면 결국, 리액트 네이티브에서 화면이 변경되는 시점은 컴포넌트의 상태값이 변경될 때니까요!또는! 우리가 한번 배웠떤 Loading.js를 이용하여 데이터가 준비가 되면 로딩 화면을 치우고, 본 화면을 보여준다던가!</aside>
      • 여러분들 마음껏 화면 처리를 해주시면 됩니다!
      • 이 개념은 어려울 수 있습니다 충분히! 따라서 처음에는, 어떠한 데이터를 보여주는 컴포넌트라면, 무조건 초기값을 의미없는 값이더라도 넣고 시작한다! 라고 생각하시면 편합니다!
      • </aside>
      • //초기 컴포넌트의 상태값을 설정 const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" })
      • <aside> 💡 DetailPage 초반에 우린 이렇게 상태값을 설정해놨었습니다. 그 이유가 뭘까요? 심지어 이 상태값을 초기에 설정안하면 오류가 발생합니다. tip엔 아무것도 없다며...
    • route.params 객체에 건네준 딕셔너리가 넘겨 있습니다!
    • 왜냐하면 지금은 어떠한 카드를 눌러도 상세 페이지에서 동일한 데이터를 보게 되잖아요? tip 데이터를 고정해놨으니까요!
    • navigation.navigate("Detail",**{** title**:** title **})**
    • <aside> 💡 버튼 카드에서 사용한 함수에 아래와 같이 두 번째 인자로 딕셔너리를 넘겨주면, 우리는 이동 한 페이지에서 넘겨준 데이터를 받을 수 있습니다.
  • </aside>
  • 그럼 이제 Card.js에 페이지 이동 기능을 달아보겠습니다. 코드를 실행 한 다음 페이지를 이동해 보면 익숙한 뒤로 가기 버튼도 보일거에요!
  • //navigation 객체가 가지고 있는 두 함수(setOptions와 navigate) //해당 페이지의 제목을 설정할 수 있음 navigation.setOptions({ title:'나만의 꿀팁' }) //Stack.screen에서 name 속성으로 정해준 이름을 지정해주면 해당 페이지로 이동하는 함수 navigation.navigate("DetailPage") //name 속성을 전달해주고, 두 번째 인자로 딕셔너리 데이터를 전달해주면, Detail 페이지에서 //두번째 인자로 전달된 딕셔너리 데이터를 route 딕셔너리로 로 받을 수 있음 navigation.navigate("DetailPage",**{ title: title }**) //전달받은 데이터를 받는 route 딕셔너리 //비구조 할당 방식으로 route에 params 객체 키로 연결되어 전달되는 데이터를 꺼내 사용 //navigate 함수로 전달되는 딕셔너리 데이터는 다음과 같은 모습이기 때문입니다. /* { route : { params :{ **title:title** } } } */ **const {** title**} = route.params;**
  • Stack.screen에 등록된 모든 페이지 컴포넌트들은 navigation 와 route 라는 딕셔너리(객체)를 속성으로 넘겨받아 사용할 수 있습니다. 이 두 딕셔너리는 다음과 같은 기능을 갖습니다.
  • <aside> 💡 Main 페이지(MainPage.js)에서 카드 버튼을 누르면(Card.js) 꿀팁 상세 페이지(DetailPage.js)로 이동합니다

06**. [Expo 앱다운 앱기능] 공유 기능과 외부 링크 추가**

  • Share그런데 결과를 친구한테 공유하고 싶은 마음이 들수도 있겠네요 결과를 공유해주고, 앱다운로드 주소까지 전달해준다면 여러분이 만든 앱 홍보하기가 수월하겠죠?
  • </aside>
  • <aside> 👉 디테일 페이지까지 이동해서 도달했다면 여러분은 앱 개발자!
  • Share 적용해보기
    • 준비!
      • 설치 할 라이브러리 없이, react-native에서 기본적으로 제공해주는 공유 기능을 사용합니다.
      import { Share } from "react-native";
      
      • 그리고 우린 다음과 같은 공유 버튼을 디테일 페이지 하단에 생성하고 기능을 만들꺼에요! 한번 잠깐 영상을 멈추고 기능은 생각 말고 다음 버튼을 추가한 화면만 그려보세요! 복습 복습
      • 버튼 추가 디테일 페이지 모습
    • [실습 ✍️] 적용</aside>
      • [코드스니펫] DetailPage.js
      • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { **Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); }** return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}**>팁 공유하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:100, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
      • 공유하는 모습
    • <aside> 💡 share 함수를 만들고 거기에 react-native가 기본적으로 제공해주는 Share 함수를 사용하여 간단히 공유를 할 수 있습니다
    • 공유 모습
  • Linking이번엔 앱에서 외부 링크를 여는 방법에 대해 배워봅니다. 버튼을 누르면, 버튼에 연결 시킨 기능을 통해 외부 링크를 핸드폰에 있는 기본 브라우저로 열어봅니다.
  • </aside>
  • <aside> 👉 디테일 페이지에 있는 내용들이 사실 어디에서 가져온거라면? 출처를 남겨야 할겁니다. 그리고 그 출처로 바로가기 버튼정도도 있으면 여러분들이 만드는 다양한 플랫폼들을 서로 연결 시킬 수 있겠죠?
  • Linking 적용해보기
    • 준비!
      • DetailPage에 버튼을 다음과 같이 추가해주세요
      • 링크 버튼 추가 된 DetailPage 모습
      • expo 에서 제공해주는 도구를 설치 한다음, 해당 도구를 상단에 가져와 준비해야 합니다
      expo install expo-linking
      
      import * as Linking from 'expo-linking';
      
    • [실습 ✍️] 적용
      • [코드스니펫] DetailPage.js
      • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; import * as Linking from 'expo-linking'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); } const link = () => { Linking.openURL("<https://spartacodingclub.kr>") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}>팁 공유하기 link()}>외부 링크 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:90, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })

07**. 3주차 끝 & 숙제 설명**

    1. 어바웃 화면 페이지화 시키고 버튼 추가하기!
    <aside> 👉 지난번 숙제로 만든 AboutPage.js를 아직 우린 스택 네비게이터에 연결하지 않았습니다.추가적으로 외부 링크 걸기까지 배웠으니, 여러분 인스타나 블로그 주소 혹은 아무주소를 AboutPage 하단 버튼에 연결해주세요!
    • 만들 화면
    • 가이드
      1. 메인에 TouchableOpacity 버튼을 두고 navigate를 연결시키면 이동이 가능하겠죠?
      2. Stack.screen에 AboutPage.js를 연결시켜야합니다!
      3. 소개 페이지 상태바의 디테일함을 주목하세요! 흰색이네요 🙂
  • </aside>
  • 따라서 여러분이 직접! 스택네비게이터에 AboutPage.js를 달아보구요! 하단 메인페이지의 상단에 다음과 같은 버튼을 만들어서 누르면 AboutPage로 가게끔 해주세요!
    1. 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요
  • 가이드
      1. 찜 데이터 제공
      <aside> 💡 나만의 꿀 팁 앱에서 찜 페이지란 쉽게 말해 팁 리스트 중에 선택한 팁들을 모아보는 페이지입니다. 이런 찜 페이지 자주 보셨죠?
      • 즉, 다음 상태값으로 화면을 구성해주세요::: 리스트에 딕셔너리 두 개가 들어있는 데이터입니다.
      </aside>
      • [코드스니펫] 찜 상태 데이터
      • const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }])
    • 하지만 아직 데이터를 저장하고, 수정하고 등등의 데이터 관리 부분을 배우지 않았기 때문에 [코드스니펫] 찜 상태 데이터를 가지고 화면만 그려볼겁니다.
      1. 파일의 위치와 이름
      <aside> 💡 새로운 페이지를 만드는 중이니, 어디에 위치해야 할까요? 당연히 pages 폴더에 위치시켜야 겠죠?</aside>
    • 또한 파일이름은 우리 LikePage.js 로 만들어 봅시다! ZzimPage는 좀...
      1. 메인의 꿀팁찜 버튼을 누르면 찜 페이지로!
      <aside> 💡 혼자만의 큰그림이 있었습니다. 메인페이지에 꿀팁찜 버튼이란걸 강의 초반부터 만들어 놓았었어요! 이 버튼을 누르면 찜 페이지로 가게끔 해봅시다!
    • </aside>
      1. Card.js 를 복붙해서 찜 페이지에서만 사용하는 LikeCard.js 만들기
      <aside> 💡 찜 목록도 Card.js와 크게 다를건 없습니다. 기존 카드에 찜 삭제 버튼정도가 추가 되었다는 점?이 다르므로, Card.js 코드를 복붙해서 LikeCard.js를 만들어 봅시다</aside>
    • 그런데! LikeCard.js에선 TouchableOpacity로 감싸는게 아닌! View 태그로 카드 전체를 감싸주도록 바꿔주세요!
      1. 최종 폴더 & 파일 모습

HW. 3주차 숙제 해설

  • 숙제 1: 어바웃 화면 페이지화 시키고 버튼 추가하기!
    • [코드스니펫] StackNavigator.js(숙제1)
    • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> </Stack.Navigator> ) } export default StackNavigator;
    • [코드스니펫] AboutPage.js(숙제1)
    • import React,{useEffect} from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' import { StatusBar } from 'expo-status-bar'; export default function AboutPage({navigation,route}){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" useEffect(()=>{ navigation.setOptions({ title:"소개 페이지", headerStyle: { backgroundColor: '#1F266A', shadowColor: "#1F266A", }, headerTintColor: "#fff", }) },[]) return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:20, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
    • [코드스니펫] MainPage.js(숙제1)
    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20, }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
  • 숙제 2: 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요!
    • [코드스니펫] StackNavigator.js(숙제2)
    • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; import LikePage from '../pages/LikePage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> <Stack.Screen name="LikePage" component={LikePage}/> </Stack.Navigator> ) } export default StackNavigator;
    • [코드스니펫] LikeCard.js(숙제2)
    • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function LikeCard({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <View style={styles.card}> <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> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
    • [코드스니펫] MainPage.js(숙제2)
    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> **<TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>** </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
    • [코드스니펫] LikePage.js(숙제2)
    • import React,{useState, useEffect} from 'react'; import {ScrollView, Text, StyleSheet} from 'react-native'; import LikeCard from '../components/LikeCard'; import Card from '../components/Card'; export default function LikePage({navigation,route}){ const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }]) useEffect(()=>{ navigation.setOptions({ title:'꿀팁 찜' }) }) return ( { tip.map((content,i)=>{ return() }) } ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#fff" } })01. 오늘 배울 것
      • 오늘 배울 내용이 두 가지는 앞으로도 계속~ 반복 숙달합니다 🙂
        1. 리액트 필수 지식 활용
        2. Expo 기능 사용
        3. 페이지 적용
        </aside>
      • 오늘은 앱다운 앱을 만드는 연습과 기술을 배울텐데요! 다음 순서대로 지금까지 배웠던 것을 복습 하면서 앱다운 앱 기능을 배우니 점점 더 여러분은 앱 개발에 숙달되는 모습을 볼 수 있습니다!
      • <aside> 👉 지난 시간까지 자바스크립트 기초체력을 다지고 JSX로 화면을 그리는 방법 까지 배워봤습니다.
      • [앱 필수 기초지식] 리액트 필수지식
        • 컴포넌트(Component)
        • 상태(State,useState)
        • 속성(Props)
        • useEffect
        이렇게 네 가지입니다. 단어가 조금 어려워 보이지만, 재미있고 간단한 개념들입니다. 이 개념들을 통해 앱 코드를 체계적으로 관리할 수 있고 화면안에서 데이터를 자유자재로 관리할 수도 있습니다.리액트 네이티브(React Native)는 리액트(React.js) 기반으로 만들어진 앱 개발 기술이기 때문이에요! 겁먹지마시고 차근차근 다루어봅시다.
      • </aside>
      • 최소한의 리액트 개념을 배우는 이유는
      • <aside> 👉 리액트 네이티브 앱을 만들기 위해서 알아야 하는 최소한의 리액트 개념을 배웁니다.
      • [Expo 앱다운 앱기능] 앱다운 앱을 위해 사용할 리액트 네이티브와 Expo 기능들이렇게 앱 다운 앱이 되기위한 기능들을 공식문서를 보고 하나하나 적용해보는 시간을 갖습니다. 이 연습을 거치면 여러분들은 공식문서를 보며 필요한 앱 기능들을 여러분들 앱에 붙여나갈 수 있습니다!
      • </aside>
      • <aside> 👉 앱이라고 하면 보통 현재 보고 있는 내용을 친구들한테 공유하거나, 앱 안에 있는 링크를 누르면 바로 해당 링크로 화면이 전환되는 기능들이 들어 있습니다. 또한 배터리를 볼 수 있는 상태바 스타일도 앱 전체적인 스타일에 맞게 변경할 수도 있어야 할겁니다.
      • [앱 페이지 적용] 앱에 페이지 기능 넣기!그래서 앱안에 여러 페이지를 두고 버튼을 눌러 이동할 수 있는 기능을 달아봅니다. 본격적으로 거의 온전한 앱의 모습을 띄게 됩니다!
      • </aside>
      • <aside> 👉 앱에 페이지가 하나라면 너무 단조롭고 볼게 없는 앱이 되겠죠?
      02. [함께해보기] 나만의 꿀팁 앱 상세 화면
      • [실습 ✍️ ] 나만의 꿀팁 상세 페이지가될 아래의 화면을 만들어보세요
      • 퀴즈 가이드
        • [현재 코드 상황]
          • App.js
          • import React from 'react' import MainPage from './pages/MainPage'; import AboutPage from './pages/AboutPage'; export default function App(){ // return (<MainPage/>) return (<AboutPage/>) }
          • MainPage.js
          • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { 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> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, 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", } });
          • AboutPage.js
          • import React from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' export default function AboutPage(){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:100, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
          • 폴더구조
        • DetailPage.js를 pages 폴더에 만들어 넣기</aside>
        • <aside> 👉 저번 1주차 마무리 시간에, pages 폴더를 만들고 AboutPage.js 파일을 만들어 넣었던 기억이 나시나요? 그때랑 동일하게 pages 폴더안에 DetailPage.js 파일을 만들고 App.js에서 return 부분에 DetailPage.js를 넣어주세요 다음과 같이요!
        • 문제 디테일 화면을 위한 데이터딕셔너리 형태로 키값에 접근해서 값을 꺼내 사용해도 되고! 문자열들 그대로 복사해서 Text 태그에 넣어 사용해도 됩니다!
          • [코드스니펫] 상세 화면에서 사용될 데이터
          • const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }
        • </aside>
        • <aside> 👉 화면에 나타낼 꿀 팁은 다음 데이터를 사용하세요.
      • 함께 해보기</aside>
        • [코드스니펫] DetailPage.js 화면
        • import React from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage() { const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" } const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
      • <aside> 👉 DetailPage를 확인하려면 App.js를 좀 바꿔야 겠죠? 어떻게 바꿔야 할까요?
      03**.** [앱 필수 기초지식] 컴포넌트, 속성, 상태, useEffect
      • 공부 할 리액트 필수 지식<aside> 👉 다시 떠올리자면, 리액트 네이티브는 리액트(React.js) 라이브러리 기반으로 만들어진 프레임워크 입니다. 그렇기 때문에 기본적인 구조는 리액트를 닯아 있습니다.1) 컴포넌트(Component) : 정해진 엘리먼트들(요소)을 사용하여 만든 화면의 일부분 2) 상태(State) : 컴포넌트에서 데이터를 유지하고 관리하기 위한 유일한 방법 == 그냥 사용할 데이터! 3) 속성(Props) : 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하는 방식 == 그냥 데이터 전달! 4) useEffect : 화면에 컴포넌트가 그려지면 처음 실행해야 하는 함수들을 모아두는 곳</aside>
      • 네 가지가 있습니다. 워워 어렵지 않아요. 이것도 정해진 규칙과 개념일 뿐! 사용방법 숙지!만 하면 그만이랍니다!
      • 대표적으로
      • 컴포넌트(Component)</aside><aside> 👉 컴포넌트는 즉, 화면의 모든 부분입니다. 그리고 컴포넌트란 App.js의 큰 App 함수처럼, 코드 전체를 감싸고 있는 함수를 뜻하기도 합니다.</aside>이렇게 컴포넌트로 생각하고 앱을 개발해 나아간다면, 코드 재사용이 용이 해집니다.실제 우리 예제에 어떻게 적용할 수 있는지. 확인해보겠습니다.
      • </aside>
      • 코드 재사용이란 어려운 용어는 아니고, 버튼 하나를 만들었을 때 이 버튼 코드를 여러 페이지에서 사용 가능하다는 뜻입니다.
      • <aside> 👉 버튼 하나가 컴포넌트가 될 수 있고, 버튼을 모아둔 영역이 컴포넌트가 될 수 있습니다.
      • 따라서 App.js를 App 컴포넌트라고도 부를 수 있습니다.
      • <aside> 👉 컴포넌트는 UI의 요소, 요소를 재사용 가능한 부분으로 조각내서 운영하는 기법입니다. 리액트 기반으로 만들어진 페이스북 웹사이트는 운영되는 컴포넌트가 수 만가지라고 합니다
      • [실습 ✍️ ] 메인화면 컴포넌트화 해보기그럼 다음 코드를 Card.js에 넣어주세요! 어디서 많이 본 코드입니다 그쵸?<aside> 🚧 마찬가지로 MainPage.js 화면을 확인하려면 App.js를 바꿔야 겠죠? return (<MainPage/>) 으로요!
        • [코드스니펫] Card.js
        • import React from "react" import {View,Text,Image,StyleSheet} from "react-native"; //비구조 할당 방식으로 넘긴 속성 데이터를 꺼내 사용함 export default function Card({content}) { return (<View style={styles.card}> <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>) } const styles = StyleSheet.create({ 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", } })
        <aside> 👉 맞습니다! MainPage.js에서 이미지위에 텍스트가 있던, 한 줄에 세 개씩 나열 되었던 그 카드 버튼입니다!.</aside>
        • [코드스니펫] MainPage.js
        • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; **import Card from '../components/Card';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (**<Card content={content} key={i}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
        <aside> 👉 MainPage.js에 있던 코드들을 Card.js로 옮겼습니다. 이렇게 옮기게 되면, 추후에 MainPage.js가 아닌 다른 페이지(페이지 또한 컴포넌트로 볼 수 있습니다) 또는 컴포넌트에서 Card.js를 사용할 수 있습니다.</aside>
        • MainPage.js
        • Card.js
        <aside> 👉 미리 말씀드리자면 MainPage.js에서 Card.js로 데이터를 넘기고 있는 모습입니다! 이를 속성을 넘긴다! 라고 보통 일컫는데 이는 다음 챕터에서 더 자세히 다루도록 하겠습니다!
      • </aside>
      • 그런데 약간 생소한 부분이 있습니다 다음 두 부분인데요!
      • 그럼 MainPage.js는 다음과 같이 변경해주세요
      • </aside>
      • </aside>
      • <aside> 👉 Components 폴더 안에 Card.js라는 파일을 만듭니다. 보통 컴포넌트들은 components 폴더에 모아두면서 관리하곤 합니다.
      • 속성(Props)<Text> 태그엔 numberOfLines이란 속성이 있었습니다. 말줌임표 효과를 주었죠! <Image> 태그엔 resizeMode란 속성이 있었습니다. 이미지가 영역을 차지하는 방식을 나타냈죠.</aside>예를 들어 Card를 사용하는 곳에서<Card image={'이미지 주소'}/> 라고 속성을 써 넣으면, 이 <Card/>는 image라는 속성이름에 {'이미지 주소'} 값을 을 갖게 됩니다. 즉 키와 벨류의 값을 갖게 됩니다 근데 이게 무슨 의미가 있을까요?</aside>
      • 의미가 있습니다. 속성을 부여받은 컴포넌트에서 해당 속성 값을 받아서 사용 할 수 있거든요! 다음과 같이 말이죠!
      • <aside> 👉 우리가 만든 <Card/> 컴포넌트에도 속성이 존재합니다. 우리가 부여 하면 부여하는 대로 속성이 되는 겁니다.
      • 이 모든 속성들은 공식 문서에 나와 있고, 해당 엘리먼트들이 태어날 때부터 가지고 있는 속성이였습니다.
      • <aside> 👉 속성은 쉽게 생각해서 컴포넌트에 데이터를 전달한다는 것이다. 그 전달 모습은 키와 벨류의 형태입니다.
      • [확인해보기👀 ]</aside>
        • MainPage.js 에서의 Card.js 컴포넌트
        <aside> 👉 어렵지 않은 규칙이 숨어 있습니다!
        1. 컴포넌트에 속성(데이터)을 부여해줘서 전달할땐, 키와 벨류(content={content}) 형태로 전달해줘야 할 것
        2. 컴포넌트를 반복문 돌릴땐, 컴포넌트마다 고유하다는 것을 표현하기 위해, map에서 나오는 인덱스(i)를 key = {i} 속성 전달 형태로 꼭 넣을것! 입니다.
        </aside>
        • Card.js 에서의 속성 값 내려 받기
        <aside> 👉 오랜만에 복습겸 나온 비구조 할당 방식 의 모습입니다. MainPage.js에서 넘겨준 속성은 실제 받게되는 컴포넌트에서 정말 딕셔너리 데이터를 받았다! 라고 생각하면 됩니다.이 방식으로 넘겨준 키값을 {키,키,키} 비구조 할당 방식으로 바로 꺼내서 사용하면 됩니다!
      • </aside>
      • 때문에, 비구조 할당 방식이 뭐였죠? 딕셔너리에서 키값을 바로 취해서 변수로써 함수안에서 즉시 사용할 수 있는 방식이였죠?
      • <aside> 👉 MainPage.js에서 방금전 컴포넌트화! 했던 Card.js를 우린 이렇게 사용했습니다.
      • 상태(State, useState)<aside> 👉 컴포넌트마다 데이터를 보유하고 관리 할 수 있습니다. 데이터라고 불러도 되지만, 리액트에서는 컴포넌트에서 보유/관리 되는 데이터를 **상태**라 부릅니다.사용방법은 실습을 통해 알아보도록 하겠습니다!
      • </aside>
      • 리액트에서 상태(state)는 리액트 라이브러리에서 제공해주는 useState로 생성하고 setState 함수로 정/변경 할 수 있습니다.
      • useState, 그렇다면 왜 중요할까?여러분이 만드는 화면은 데이터에 따라 변경됩니다 또 아무 데이터가 아니라 이 상태(state)로 관리되는 데이터가 변경되면 화면이 바뀝니다.</aside><aside> 👉 당시 개발자들끼리 이 상태와 컴포넌트의 관계를 공식으로 표현한 글을 보고 대단하다!! 했던 기억이 있는데, 여러분들은 어떻게 느끼실지 궁금하네요 ㅎㅎ무슨말인지 아직 잘 와닿지 않으시죠? 다른건 잘 와닿지 않아도 state는 컴포넌트에서 관리되는 상태만 기억하시면 충분합니다 이 개념은 이번 강의에서 개념을 다 배운 뒤, 다음 6 강에서 실습 하며 직접 확인할 수 있습니다
      • </aside>
      • 저 공식은 즉, 사용자 화면(UI)는 컴포넌트(component)에 어떤 데이터(state)가 주입되고 변경되냐에 따라 변화된다를 뜻합니다
      • UI = component(state)
      • 모르셔도 되지만 꽤 유의미한 공식이 있습니다
      • <aside> 👉 리액트는 특이한 점이 있습니다.
      • 화면이 그려진다음 가장 먼저 실행되는 함수, useEffect화면이 그려진 다음 가장 먼저 실행되는 함수로 사용방법이 간단합니다 형식만 알고 있으면 충분합니다
        **useEffect(()=>{**
        
        	...화면이 그려진 다음 가장 먼저 실행되야 할 코드 작성 공간
        
        **},[])**
        
        <aside> 👉 useEffect(()=>{ },[])</aside>
      • 이 안에서, 화면이 그려진다음 실행시키고 싶은 함수를 작성한다면 가장먼저 실행이 됩니다. useEffect 또한 어떤 역할을 하는지만 알고! 바로 실습을 통해 확인해보도록 하겠습니다
      • </aside>
      • <aside> 👉 useEffect는 너무 간단하고 유용하게 쓰이는 리액트 기본 제공 함수입니다.
      • useEffect, 그렇다면 왜 중요할까?
        1. 화면이 그려진다
        2. useEffect가 데이터를 준비한다
        3. 상태 데이터가 업데이트 되었으니 화면이 다시 그려진다
        화면이 그려진 다음, 서버에게 필요한 데이터를 요청하여 받은 후, 화면을 다시그릴때 주로 사용되는데요! 아주 일반적인 패턴이므로 여러분들도 금방 이해하고 익숙해지게 됩니다!
      • </aside>
      • <aside> 👉 보통 useEffect는 데이터를 준비할 때 사용합니다 데이터를 준비한다는 것은, 데이터를 서버로부터 혹은 어디선가로부터 받은 후 상태(state)에 반영한다는 것을 뜻합니다. 이런순서로 말이죠
      • [실습✍️ ] data.json을 상태(state)에 넣어서 관리해보기복습하실때 주석도 자세히 살펴보세요!
        • [코드스니펫] MainPage.js
        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //useState 사용법 //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수 //setState는 state를 변경시킬때 사용해야하는 함수 //모두 다 useState가 선물해줌 //useState()안에 전달되는 값은 state 초기값 const [state,setState] = useState([]) //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수 //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음 useEffect(()=>{ setState(data) },[]) // let tip = data.tip; //data.json 데이터는 state에 담기므로 상태에서 꺼내옴 let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
        <aside> 👉 그러나 실행을 하면 다음과 같은 오류 화면이 나옵니다
        • 오류화면
        <aside> 👉 그 이유는 이런 절차로 발생됩니다.
        1. 화면이 그려진다
        2. useEffect가 데이터를 state에 useState를 이용하여 업데이트한다
        3. 상태(state)가 변경되었으니 화면이 다시 그려진다
        여기서 1번에서 오류가 난겁니다. 화면이 가장 먼저 그려질때, 우리가 Card에 데이터를 넘기기위해 어떤일을 했나요?이런 오류는 우리가 지금 배운 개념들, 상태, 컴포넌트, useEffect를 이용하여 로딩화면을 만들어줌으로써 해결이 가능합니다!
      • </aside>
      • 처음부터 데이터가 없어서 , 꺼내올수 없는 오류
      • <aside> 👉 상태(state)에서 tip 키 값에 접근하여 데이터를 꺼내 map 즉, 반복문을 돌리려했습니다. 그런데 state에 데이터가 처음부터 있었나요? 없었죠? useEffect를 거쳐야 state에 데이터가 담기는데 아직 없는 상태라 오류가 나는 겁니다.
      • </aside>
      • </aside>
      • </aside>
      • <aside> 👉 지금 꿀팁을 data.json 파일에서 꺼내 바로 JSX 문법에서 사용하고 있습니다. 이를 다음과 같이 상태에 저장한 다음 사용하도록 변경해보도록 하겠습니다.
      04. [앱 필수 기초지식 응용] 로딩화면, 카테고리 기능, 상태 바
      • 앱 운영에 필요한 로딩화면이때 우린 로딩화면을 만들어줌으로써 쉽게 해결이 가능합니다. 컴포넌트 폴더에 Loading.js 파일을 만들어주고 다음 코드를 넣어주세요
        • [코드스니펫] Loading.js
        • import React from 'react'; import {View,Text,StyleSheet} from 'react-native'; export default function Loading(){ return(<View style={styles.container}><Text style={styles.title}>준비중입니다...</Text></View>) } const styles = StyleSheet.create({ container: { //앱의 배경 색 flex:1, justifyContent:'center', alignItems:'center', backgroundColor: '#fdc453', }, title: { fontSize:20, fontWeight:'700' } })
        <aside> 👉 그리고 MainPage.js 다음 코드 스니펫을 넣어주세요
        • [코드스니펫] MainPage.js
        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 const [state,setState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. **const [ready,setReady] = useState(true)** useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 **setTimeout(()=>{ setState(data) setReady(false) },1000)** },[]) // let tip = data.tip; let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" **//처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐)** return **ready ? <Loading/>** : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
        • 적용 모습
        <aside> 👉 ready 라는 새로운 상태값이 추가되었습니다. 즉, 상태값은 컴포넌트안에 여러개가 될 수 있습니다.즉, 이런 순서로 진행된거에요!
        1. ready 값이 true이므로 return 구문에서 ? 물음표 바로 뒤의 Loading 컴포넌트가 화면에 그려짐
        2. 화면이 그려지고 난다음, 1초 이따가 상태값들이 채워지고 변경됨
        3. ready 상태 값이 false가 됨
        4. 상태값이 변경되었으므로 화면이 다시 그려짐
        5. ready 값이 false 이므로 return 구문에서 : 콜론 뒤의 MainPage 컴포넌트가 화면에 그려짐
        차근차근 순서따라 반복해서 보면 별거아닙니다. 정해진 규칙에 따라 우린 코드를 적용했고 정해진 규칙에 따라 코드가 실행되고 있는 거에요!
      • </aside>
      • useEffect안에 setTimeout이라는 함수가 존재합니다. , 뒤에 있는 숫자 (1000 === 1초) 만큼, 지연됐다가 안에 있는 코드가 실행되는 지연 함수입니다.
      • </aside>
      • </aside>
      • <aside> 👉 5장에서 오류가 났던 것 처럼 화면이 그려질때 준비된 데이터가 없다!? 이러한 상황은 아주 빈번합니다.
      • 카테고리 버튼상태값이 바뀌면 화면이 다시 그려진다! 라는 개념을 염두하고 한번 만들어보겠습니다!
      • </aside>
      • <aside> 👉 우리에게 이런 버튼이 있었습니다. 카테고리 버튼인데요! 이 버튼은 장식이아니라, 실제 누르면 하단의 데이터가 카테고리에 맞게 다시 정렬되는 기능이 의도되어 있습니다.
      • [실습✍️ ] 카테고리 기능 넣기그리곤 함수가 필요합니다. 우리가 만든, 생활 재테크 반려견 버튼에 연결할 함수인데요!? 이 함수는 카테고리에 따라 카테고리 상태데이터를 새롭게 구성해주는 기능을 합니다.
        • [코드스니펫] MainPage.js
        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 **const [state,setState] = useState([])** //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 **const [cateState,setCateState] = useState([])** //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 **let tip = data.tip; setState(tip) setCateState(tip) setReady(false)** },1000) },[]) **const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } }** let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> **<TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>** <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, **middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 },** 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" }, **middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" },** cardContainer: { marginTop:10, marginLeft:10 }, });
        • 제태크를 눌렀을때의 모습
      • </aside>
      • <aside> 👉 카테고리 기능을 위해선 먼저 카테고리 상태가 필요합니다. 카테고리에 맞는 꿀팁들을 지속적으로 저장하고 관리할 상태 그릇이죠!
      • Expo에서 제공해주는 앱다운 앱 기능들</aside>
      • <aside> 💡 Expo SDK. 일명 Expo에서 제공해주는 앱 기능 도구들을 개발할 때 여기서 확인 할 수 있습니다. (링크) 필요한 기능들이 있는지 목록을 쭉 보고, 원하는 기능을 선택해서 적용하면 끝!
      • 상태 바란?</aside><aside> 💡 배터리가 충분한지, 몇시인지 매일 보시죠? 이부분도 우리가 처리를 할 수 있습니다.
      • </aside>
      • <aside> 💡 앱이 앱에 따라 모바일 맨 위 상태 바가 변하는 앱이 있습니다. 상태바란 이런 겁니다. 여러분들 매일 같이 보는 부분이에요!
      • StatusBar
        • 본격적으로 라이브러리 설치 시작!</aside><aside> 👉 터미널 우측 버튼중에 분할이란 버튼이 있습니다.</aside>
        • 여러분들 터미널 왼편은 서버를 켜고 끄고를 담당하고(expo start) 터미널 우측은 필요한 라이브러리들을 설치할 때 명령어를 치는 장소로 분할해서 사용하면 편리합니다!
        • <aside> 👉 이제 본격적으로 라이브러리들을 설치합니다. vscode에서 터미널을 이렇게 변경해주세요
        • [코드스니펫] Expo 상태 바 설치
        • expo install expo-status-bar
        • [실습 ✍️] 적용적용 모습을 살펴볼까요?
          • [코드스니펫] MainPage에 StatusBar 설치
          • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; **import { StatusBar } from 'expo-status-bar';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> **<StatusBar style="black" />** <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
          <aside> 💡 style이 light일 때와, black일 때가 다릅니다. 우린 앱 화면 배경색을 검은색으로 설정했기 떄문에, 상태바를 black으로 하면 보이지 않습니다.Status Bar 공식문서
          • black 일때
          • light 일때
        • </aside>
        • 상태 바 속성은 공식문서에 다양하게 존재하니, 살펴보면서 앱에 적합한 상태 바를 적용해보세요!
        • </aside>
        • <aside> 💡 컴포넌트마다 다르게 적용할 수도 있고, 앱 전체에 공통적으로 적용할 수 있습니다. StatusBar를 컴포넌트 각각 다르게 둘 수 있습니다 우린 MainPage.js에 달아볼거에요!
      05**. [앱** 페이지 적용**] 네비게이터 사용하기**
      • 네비게이션이란?<aside> 👉 앱에 페이지 개념을 입혀주고! 웹 사이트를 이용하듯, 앱에서 여러분들이 만든 컴포넌트들을 페이지화 시켜주고, 해당 페이지끼리 이동을 가능하게 해주는 라이브러리입니다가령 다음 화면과 같이, 앱 하단에 탭 버튼을 두고 해당 버튼을 누르면 연결되어 있는 페이지로 바로바로 이동할 수 있게 해주는 기능(열 앱에서 많이 보셨죠?)도 가지고 있습니다.</aside>
      • 따라서 이번시간에 같이 공부한 다음 틈틈히 다른 기능들도 적용해보세요!
      • 이번 강의에선 앱 내의 페이지 구성 및 이동까지 직접 적용해볼 텐데요!, 이 라이브러리는 다양한 기능들을 가지고 있습니다.
      • <aside> 💡 결국 이 라이브러리도 Expo에서 지원하고 있는 도구로써, 앱을 만들기 위해 사용 중인 Expo와 궁합이 아주 잘 맞습니다.
      • </aside>
      • <aside> 💡 물론 이것 또한 쉽게 페이징을 도와주는 외부 라이브러리를 가져다가 사용합니다. react-navigation 공식문서 보러가기 —> (링크)
      • </aside>
      • 현재 갖추고 있는 페이지 구성 확인!</aside>
      • <aside> 💡 페이지 구성은 보통 pages 폴더 안에 생성해둔 컴포넌트로 구성합니다. 우리는 이미, MainPage.js 메인 페이지 DetailPage.js 상세 화면 페이지 그리고 숙제로 만든 소개화면인 AboutPage.js 까지 총 3 페이지를 가지고 있습니다.
      • 기본 설치 코드우리에게 필요한 것이라고 했지만, 거의 대부분의 앱에 적용이 되어 있는 것들만 가져왔습니다.아래 명령어를 차례차례 터미널에 넣어 실행해주세요 가장 마지막줄은 꽤 깁니다. 이렇게 여러 라이브러리들을 띄어쓰기로 연결해서 한번에 설치 할 수도 있습니다.
        • [코드스니펫] 네비게이션 설치 코드
        • yarn add @react-navigation/native
        • [코드스니펫] 네비게이션 추가 설치코드
        • expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
      • </aside>
      • 아래 명령어들은 네비게이션을 사용하기 위해 필요한 기본 라이브러리들이구요! 우리가 곧 배울 스택 네비게이션와 탭 네비게이션 기능은 추가적으로 라이브러리를 또 설치 해줘야 합니다 😂
      • <aside> 💡 리액트 네이티브에서 페이지 네비게이션을 구현하기란 조금 까다롭습니다. 그래서 딱 우리에게 필요한 것만 가져와 설치 하고, 적용해 나갈 계획입니다.
      • 스택 네비게이션이란?</aside><aside> 💡 컴포넌트를 페이지화 시키는 스택 네비게이션은 다음과 같습니다.이렇게 만든 여러 페이지들을 책갈피 기능을 하는 스택 네비게이터에 모조리 등록시켜서, 언제든지 이 페이지 이동이 가능하게끔 해줍니다</aside>
      • 페이지는 Stack.Screen 이라 부르며 책갈피는 Stack.Navigator라 부릅니다
      • 우리가 페이지처럼 만든 컴포넌트를, 정말 페이지처럼 사용할 수 있게끔 페이지로 컴포넌트를 감싸 페이지로 만들어줍니다.
      • <aside> 💡 스택 네비게이션은 컴포넌트에 페이지 기능을 부여해주고 컴포넌트에서 컴포넌트로 이동, 즉 페이지 이동을 가능하게 해줍니다
      • createStackNavigator 사용해보기
        • [코드스니펫] 스택 네비게이터 설치 코드
        • yarn add @react-navigation/stack
        • [실습 ✍️] 적용하기</aside>
          • [코드스니펫] StackNavigator.js 스택 네비게이터
          • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
          <aside> 👉 워워 진정하세요! 위에서부터 차근차근 살펴볼거에요 🙂 언제나 우린 미니카 조립 설명서를 보면서 조립하듯, 공식 문서 내용 그대로 적용한다는 것 꼭 잊지마시고! 살펴보도록 하겠습니다.
          • 💻스택 네비게이터 코드 분석
            • 공식 문서 링크
            • 적용 순서 1) 사용 준비
            • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator();
            • 적용 순서 2) 기본 틀
            • //리액트의 모~든 파일은 컴포넌트라 생각하고 //페이지 기능을 해주는 모든 기능이 담겨 있는 컴포넌트를 만든다 생각하세요! const StackNavigator = () =>{ return ( /// 페이지 기능이 들어갈 곳 ) } export default StackNavigator;
            • 적용 순서 3) 스크린 옵션
            • //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
            • 적용 순서 4) 페이지 연결
            • {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/>
          <aside> 👉 컴포넌트를 페이지화 했고, 페이지를 이동할 수 있는 네이게이션도 준비가 됐다면, 우리는 최상단 컴포넌트 즉 App.js에 네비게이션 기능을 달아야 합니다.</aside>
          • [코드스니펫] App.js
          • import React from 'react'; //이제 모든 페이지 컴포넌트들이 끼워져있는 책갈피를 메인에 둘예정이므로 //컴포넌트를 더이상 불러오지 않아도 됩니다. // import MainPage from './pages/MainPage'; // import DetailPage from './pages/DetailPage'; import { StatusBar } from 'expo-status-bar'; //메인에 세팅할 네비게이션 도구들을 가져옵니다. import {NavigationContainer} from '@react-navigation/native'; import StackNavigator from './navigation/StackNavigator' export default function App() { console.disableYellowBox = true; return ( <NavigationContainer> <StatusBar style="black" /> <StackNavigator/> </NavigationContainer>); }
          • 스택 네비게이터 적용 후 MainPage 모습
        • 즉, 앱 가장 최상위 코드에 네비게이션을 다는겁니다 그래야 앱 어디서든 원하는 페이지 이동이 가능할테니까요!
        • </aside>
        • <aside> 👉 navigation 폴더 하나를 만들고 StackNavigator.js 파일을 만들어주세요 그리고 StackNavigator 안에 다음 코드를 넣어주세요
      • 페이지 헤더 수정</aside><aside> 💡 그 이유는 StackNavigator에서 현재 헤더에 대한 스타일과 페이지에 헤더의 제목까지 결정해주고 있기 때문입니다.
        • 스택 네비게이터의 헤더 스타일 부분 코드
        • <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTintColor: "#fff", headerBackTitleVisible: false }} > {/* name에 해당 하는 부분이 페이지의 타이틀이 됩니다.*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
        <aside> 💡 따라서 헤더 스타일을 적절히 바꾸기 위해 다음과 같이 옵션 부분의 코드를 수정해보도록 하겠습니다.
        • [코드스니펫] StackNavigator.js
        • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
        • [코드스니펫] MainPage.js 에서 title 삭제!
        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
        • StackNavigator 옵션 코드 수정 후 모습
        <aside> 💡 그래도 위에 페이지 제목으로 MainPage가 되는 모습은 뭔가 또 수정이 필요해보입니다. 이를 위해선 다음 스택 네비게이션 페이지 이동 강의에서 살펴보도록 하겠습니다
      • </aside>
      • </aside>
      • </aside>
      • <aside> 💡 그런데 현재 MainPage 화면은 다음과 같이 상단의 모습이 약간 이상해 보입니다.
      • 페이지 이동하기일단 페이지를 이동시키려면, 책갈피가 페이지들에게 부여해준 페이지 이동 기능을 사용해야 합니다.</aside><aside> 💡 일단 먼저 구조를 보여드렸습니다. 물론 사용을 해봐야 감이 오겠죠?같이 해보죠!
        • [실습 ✍️] 데이터 없이 페이지 이동하기
          • [코드스니펫] MainPage.js
          • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (**<Card content={content} key={i} navigation={navigation}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
          • [코드스니펫] Card.js
          • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 **export default function Card({content,navigation}){** return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage')}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
        • navigation.navigate("DetailPage")
        • [실습 ✍️] 데이터 가지고 페이지 이동하기</aside><aside> 💡 그럼 Card에서 DetailPage로 이동할 때, MainPage로 부터 넘겨받은 content도 넘겨볼까요?</aside>
          • [코드스니펫] Card.js
          • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function Card({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage',**content**)}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
          • [코드스니펫] DetailPage.js
          • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage({navigation,route}) { //초기 컴포넌트의 상태값을 설정 //state, setState 뿐 아니라 이름을 마음대로 지정할 수 있음! const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) //Card.js에서 navigation.navigate 함수를 쓸때 두번째 인자로 content를 넘겨줬죠? //content는 딕셔너리 그 자체였으므로 route.params에 고대~로 남겨옵니다. //즉, route.params 는 content죠! navigation.setOptions({ //setOptions로 페이지 타이틀도 지정 가능하고 title:route.params.title, //StackNavigator에서 작성했던 옵션을 다시 수정할 수도 있습니다. headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
          • 이동한 디테일 페이지 모습
          <aside> 💡 건네 받은 값을 꺼낼 땐, 다음과 같이 책갈피가 navigation과 추가적으로 건네준 route에서 꺼내 확인 할 수 있습니다.</aside>
          • route에 담겨져 오는 데이터 콘솔에서 직접 확인
          • DetailPage에서 상태값을 초기에 설정한 이유</aside><aside> 💡 그 이유는 컴포넌트가 화면에 그려지는 순서에 있습니다. 이건 지금 단계에선 조금 과한감이 있어서 간략히만 설명해보면 다음과 같습니다.
            1. DetailPage 컴포넌트가 useState에 들어 있는 
               데이터 가지고 화면에 그려짐(return 함수실행)
            2. 화면에 다 그려진후, useEffect 함수 실행
            3. useEffect에서 상태값 변경 이벤트가 실행되면 변경된 데이터 가지고 다시 return 실행
            4. 변경된 데이터를 가지고 화면에 DetailPage가 다시 그려짐.
            
            <aside> 💡 곰곰히 생각해보면 결국, 리액트 네이티브에서 화면이 변경되는 시점은 컴포넌트의 상태값이 변경될 때니까요!또는! 우리가 한번 배웠떤 Loading.js를 이용하여 데이터가 준비가 되면 로딩 화면을 치우고, 본 화면을 보여준다던가!</aside>
          • 여러분들 마음껏 화면 처리를 해주시면 됩니다!
          • 이 개념은 어려울 수 있습니다 충분히! 따라서 처음에는, 어떠한 데이터를 보여주는 컴포넌트라면, 무조건 초기값을 의미없는 값이더라도 넣고 시작한다! 라고 생각하시면 편합니다!
          • </aside>
          • //초기 컴포넌트의 상태값을 설정 const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" })
          • <aside> 💡 DetailPage 초반에 우린 이렇게 상태값을 설정해놨었습니다. 그 이유가 뭘까요? 심지어 이 상태값을 초기에 설정안하면 오류가 발생합니다. tip엔 아무것도 없다며...
        • route.params 객체에 건네준 딕셔너리가 넘겨 있습니다!
        • 왜냐하면 지금은 어떠한 카드를 눌러도 상세 페이지에서 동일한 데이터를 보게 되잖아요? tip 데이터를 고정해놨으니까요!
        • navigation.navigate("Detail",**{** title**:** title **})**
        • <aside> 💡 버튼 카드에서 사용한 함수에 아래와 같이 두 번째 인자로 딕셔너리를 넘겨주면, 우리는 이동 한 페이지에서 넘겨준 데이터를 받을 수 있습니다.
      • </aside>
      • 그럼 이제 Card.js에 페이지 이동 기능을 달아보겠습니다. 코드를 실행 한 다음 페이지를 이동해 보면 익숙한 뒤로 가기 버튼도 보일거에요!
      • //navigation 객체가 가지고 있는 두 함수(setOptions와 navigate) //해당 페이지의 제목을 설정할 수 있음 navigation.setOptions({ title:'나만의 꿀팁' }) //Stack.screen에서 name 속성으로 정해준 이름을 지정해주면 해당 페이지로 이동하는 함수 navigation.navigate("DetailPage") //name 속성을 전달해주고, 두 번째 인자로 딕셔너리 데이터를 전달해주면, Detail 페이지에서 //두번째 인자로 전달된 딕셔너리 데이터를 route 딕셔너리로 로 받을 수 있음 navigation.navigate("DetailPage",**{ title: title }**) //전달받은 데이터를 받는 route 딕셔너리 //비구조 할당 방식으로 route에 params 객체 키로 연결되어 전달되는 데이터를 꺼내 사용 //navigate 함수로 전달되는 딕셔너리 데이터는 다음과 같은 모습이기 때문입니다. /* { route : { params :{ **title:title** } } } */ **const {** title**} = route.params;**
      • Stack.screen에 등록된 모든 페이지 컴포넌트들은 navigation 와 route 라는 딕셔너리(객체)를 속성으로 넘겨받아 사용할 수 있습니다. 이 두 딕셔너리는 다음과 같은 기능을 갖습니다.
      • <aside> 💡 Main 페이지(MainPage.js)에서 카드 버튼을 누르면(Card.js) 꿀팁 상세 페이지(DetailPage.js)로 이동합니다
      06**. [Expo 앱다운 앱기능] 공유 기능과 외부 링크 추가**
      • Share그런데 결과를 친구한테 공유하고 싶은 마음이 들수도 있겠네요 결과를 공유해주고, 앱다운로드 주소까지 전달해준다면 여러분이 만든 앱 홍보하기가 수월하겠죠?
      • </aside>
      • <aside> 👉 디테일 페이지까지 이동해서 도달했다면 여러분은 앱 개발자!
      • Share 적용해보기
        • 준비!
          • 설치 할 라이브러리 없이, react-native에서 기본적으로 제공해주는 공유 기능을 사용합니다.
          import { Share } from "react-native";
          
          • 그리고 우린 다음과 같은 공유 버튼을 디테일 페이지 하단에 생성하고 기능을 만들꺼에요! 한번 잠깐 영상을 멈추고 기능은 생각 말고 다음 버튼을 추가한 화면만 그려보세요! 복습 복습
          • 버튼 추가 디테일 페이지 모습
        • [실습 ✍️] 적용</aside>
          • [코드스니펫] DetailPage.js
          • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { **Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); }** return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}**>팁 공유하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:100, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
          • 공유하는 모습
        • <aside> 💡 share 함수를 만들고 거기에 react-native가 기본적으로 제공해주는 Share 함수를 사용하여 간단히 공유를 할 수 있습니다
        • 공유 모습
      • Linking이번엔 앱에서 외부 링크를 여는 방법에 대해 배워봅니다. 버튼을 누르면, 버튼에 연결 시킨 기능을 통해 외부 링크를 핸드폰에 있는 기본 브라우저로 열어봅니다.
      • </aside>
      • <aside> 👉 디테일 페이지에 있는 내용들이 사실 어디에서 가져온거라면? 출처를 남겨야 할겁니다. 그리고 그 출처로 바로가기 버튼정도도 있으면 여러분들이 만드는 다양한 플랫폼들을 서로 연결 시킬 수 있겠죠?
      • Linking 적용해보기
        • 준비!
          • DetailPage에 버튼을 다음과 같이 추가해주세요
          • 링크 버튼 추가 된 DetailPage 모습
          • expo 에서 제공해주는 도구를 설치 한다음, 해당 도구를 상단에 가져와 준비해야 합니다
          expo install expo-linking
          
          import * as Linking from 'expo-linking';
          
        • [실습 ✍️] 적용
          • [코드스니펫] DetailPage.js
          • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; import * as Linking from 'expo-linking'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); } const link = () => { Linking.openURL("<https://spartacodingclub.kr>") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}>팁 공유하기 link()}>외부 링크 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:90, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
      07**. 3주차 끝 & 숙제 설명**
        1. 어바웃 화면 페이지화 시키고 버튼 추가하기!
        <aside> 👉 지난번 숙제로 만든 AboutPage.js를 아직 우린 스택 네비게이터에 연결하지 않았습니다.추가적으로 외부 링크 걸기까지 배웠으니, 여러분 인스타나 블로그 주소 혹은 아무주소를 AboutPage 하단 버튼에 연결해주세요!
        • 만들 화면
        • 가이드
          1. 메인에 TouchableOpacity 버튼을 두고 navigate를 연결시키면 이동이 가능하겠죠?
          2. Stack.screen에 AboutPage.js를 연결시켜야합니다!
          3. 소개 페이지 상태바의 디테일함을 주목하세요! 흰색이네요 🙂
      • </aside>
      • 따라서 여러분이 직접! 스택네비게이터에 AboutPage.js를 달아보구요! 하단 메인페이지의 상단에 다음과 같은 버튼을 만들어서 누르면 AboutPage로 가게끔 해주세요!
        1. 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요
      • 가이드
          1. 찜 데이터 제공
          <aside> 💡 나만의 꿀 팁 앱에서 찜 페이지란 쉽게 말해 팁 리스트 중에 선택한 팁들을 모아보는 페이지입니다. 이런 찜 페이지 자주 보셨죠?
          • 즉, 다음 상태값으로 화면을 구성해주세요::: 리스트에 딕셔너리 두 개가 들어있는 데이터입니다.
          </aside>
          • [코드스니펫] 찜 상태 데이터
          • const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }])
        • 하지만 아직 데이터를 저장하고, 수정하고 등등의 데이터 관리 부분을 배우지 않았기 때문에 [코드스니펫] 찜 상태 데이터를 가지고 화면만 그려볼겁니다.
          1. 파일의 위치와 이름
          <aside> 💡 새로운 페이지를 만드는 중이니, 어디에 위치해야 할까요? 당연히 pages 폴더에 위치시켜야 겠죠?</aside>
        • 또한 파일이름은 우리 LikePage.js 로 만들어 봅시다! ZzimPage는 좀...
          1. 메인의 꿀팁찜 버튼을 누르면 찜 페이지로!
          <aside> 💡 혼자만의 큰그림이 있었습니다. 메인페이지에 꿀팁찜 버튼이란걸 강의 초반부터 만들어 놓았었어요! 이 버튼을 누르면 찜 페이지로 가게끔 해봅시다!
        • </aside>
          1. Card.js 를 복붙해서 찜 페이지에서만 사용하는 LikeCard.js 만들기
          <aside> 💡 찜 목록도 Card.js와 크게 다를건 없습니다. 기존 카드에 찜 삭제 버튼정도가 추가 되었다는 점?이 다르므로, Card.js 코드를 복붙해서 LikeCard.js를 만들어 봅시다</aside>
        • 그런데! LikeCard.js에선 TouchableOpacity로 감싸는게 아닌! View 태그로 카드 전체를 감싸주도록 바꿔주세요!
          1. 최종 폴더 & 파일 모습
      HW. 3주차 숙제 해설
      • 숙제 1: 어바웃 화면 페이지화 시키고 버튼 추가하기!
        • [코드스니펫] StackNavigator.js(숙제1)
        • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> </Stack.Navigator> ) } export default StackNavigator;
        • [코드스니펫] AboutPage.js(숙제1)
        • import React,{useEffect} from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' import { StatusBar } from 'expo-status-bar'; export default function AboutPage({navigation,route}){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" useEffect(()=>{ navigation.setOptions({ title:"소개 페이지", headerStyle: { backgroundColor: '#1F266A', shadowColor: "#1F266A", }, headerTintColor: "#fff", }) },[]) return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:20, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
        • [코드스니펫] MainPage.js(숙제1)
        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20, }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
      • 숙제 2: 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요!
        • [코드스니펫] StackNavigator.js(숙제2)
        • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; import LikePage from '../pages/LikePage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> <Stack.Screen name="LikePage" component={LikePage}/> </Stack.Navigator> ) } export default StackNavigator;
        • [코드스니펫] LikeCard.js(숙제2)
        • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function LikeCard({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <View style={styles.card}> <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> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
        • [코드스니펫] MainPage.js(숙제2)
        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> **<TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>** </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
        • [코드스니펫] LikePage.js(숙제2)
        • import React,{useState, useEffect} from 'react'; import {ScrollView, Text, StyleSheet} from 'react-native'; import LikeCard from '../components/LikeCard'; import Card from '../components/Card'; export default function LikePage({navigation,route}){ const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }]) useEffect(()=>{ navigation.setOptions({ title:'꿀팁 찜' }) }) return ( { tip.map((content,i)=>{ return() }) } ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#fff" } })01. 오늘 배울 것
          • 오늘 배울 내용이 두 가지는 앞으로도 계속~ 반복 숙달합니다 🙂
            1. 리액트 필수 지식 활용
            2. Expo 기능 사용
            3. 페이지 적용
            </aside>
          • 오늘은 앱다운 앱을 만드는 연습과 기술을 배울텐데요! 다음 순서대로 지금까지 배웠던 것을 복습 하면서 앱다운 앱 기능을 배우니 점점 더 여러분은 앱 개발에 숙달되는 모습을 볼 수 있습니다!
          • <aside> 👉 지난 시간까지 자바스크립트 기초체력을 다지고 JSX로 화면을 그리는 방법 까지 배워봤습니다.
          • [앱 필수 기초지식] 리액트 필수지식
            • 컴포넌트(Component)
            • 상태(State,useState)
            • 속성(Props)
            • useEffect
            이렇게 네 가지입니다. 단어가 조금 어려워 보이지만, 재미있고 간단한 개념들입니다. 이 개념들을 통해 앱 코드를 체계적으로 관리할 수 있고 화면안에서 데이터를 자유자재로 관리할 수도 있습니다.리액트 네이티브(React Native)는 리액트(React.js) 기반으로 만들어진 앱 개발 기술이기 때문이에요! 겁먹지마시고 차근차근 다루어봅시다.
          • </aside>
          • 최소한의 리액트 개념을 배우는 이유는
          • <aside> 👉 리액트 네이티브 앱을 만들기 위해서 알아야 하는 최소한의 리액트 개념을 배웁니다.
          • [Expo 앱다운 앱기능] 앱다운 앱을 위해 사용할 리액트 네이티브와 Expo 기능들이렇게 앱 다운 앱이 되기위한 기능들을 공식문서를 보고 하나하나 적용해보는 시간을 갖습니다. 이 연습을 거치면 여러분들은 공식문서를 보며 필요한 앱 기능들을 여러분들 앱에 붙여나갈 수 있습니다!
          • </aside>
          • <aside> 👉 앱이라고 하면 보통 현재 보고 있는 내용을 친구들한테 공유하거나, 앱 안에 있는 링크를 누르면 바로 해당 링크로 화면이 전환되는 기능들이 들어 있습니다. 또한 배터리를 볼 수 있는 상태바 스타일도 앱 전체적인 스타일에 맞게 변경할 수도 있어야 할겁니다.
          • [앱 페이지 적용] 앱에 페이지 기능 넣기!그래서 앱안에 여러 페이지를 두고 버튼을 눌러 이동할 수 있는 기능을 달아봅니다. 본격적으로 거의 온전한 앱의 모습을 띄게 됩니다!
          • </aside>
          • <aside> 👉 앱에 페이지가 하나라면 너무 단조롭고 볼게 없는 앱이 되겠죠?
          02. [함께해보기] 나만의 꿀팁 앱 상세 화면
          • [실습 ✍️ ] 나만의 꿀팁 상세 페이지가될 아래의 화면을 만들어보세요
          • 퀴즈 가이드
            • [현재 코드 상황]
              • App.js
              • import React from 'react' import MainPage from './pages/MainPage'; import AboutPage from './pages/AboutPage'; export default function App(){ // return (<MainPage/>) return (<AboutPage/>) }
              • MainPage.js
              • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { 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> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, 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", } });
              • AboutPage.js
              • import React from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' export default function AboutPage(){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:100, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
              • 폴더구조
            • DetailPage.js를 pages 폴더에 만들어 넣기</aside>
            • <aside> 👉 저번 1주차 마무리 시간에, pages 폴더를 만들고 AboutPage.js 파일을 만들어 넣었던 기억이 나시나요? 그때랑 동일하게 pages 폴더안에 DetailPage.js 파일을 만들고 App.js에서 return 부분에 DetailPage.js를 넣어주세요 다음과 같이요!
            • 문제 디테일 화면을 위한 데이터딕셔너리 형태로 키값에 접근해서 값을 꺼내 사용해도 되고! 문자열들 그대로 복사해서 Text 태그에 넣어 사용해도 됩니다!
              • [코드스니펫] 상세 화면에서 사용될 데이터
              • const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }
            • </aside>
            • <aside> 👉 화면에 나타낼 꿀 팁은 다음 데이터를 사용하세요.
          • 함께 해보기</aside>
            • [코드스니펫] DetailPage.js 화면
            • import React from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage() { const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" } const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
          • <aside> 👉 DetailPage를 확인하려면 App.js를 좀 바꿔야 겠죠? 어떻게 바꿔야 할까요?
          03**.** [앱 필수 기초지식] 컴포넌트, 속성, 상태, useEffect
          • 공부 할 리액트 필수 지식<aside> 👉 다시 떠올리자면, 리액트 네이티브는 리액트(React.js) 라이브러리 기반으로 만들어진 프레임워크 입니다. 그렇기 때문에 기본적인 구조는 리액트를 닯아 있습니다.1) 컴포넌트(Component) : 정해진 엘리먼트들(요소)을 사용하여 만든 화면의 일부분 2) 상태(State) : 컴포넌트에서 데이터를 유지하고 관리하기 위한 유일한 방법 == 그냥 사용할 데이터! 3) 속성(Props) : 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하는 방식 == 그냥 데이터 전달! 4) useEffect : 화면에 컴포넌트가 그려지면 처음 실행해야 하는 함수들을 모아두는 곳</aside>
          • 네 가지가 있습니다. 워워 어렵지 않아요. 이것도 정해진 규칙과 개념일 뿐! 사용방법 숙지!만 하면 그만이랍니다!
          • 대표적으로
          • 컴포넌트(Component)</aside><aside> 👉 컴포넌트는 즉, 화면의 모든 부분입니다. 그리고 컴포넌트란 App.js의 큰 App 함수처럼, 코드 전체를 감싸고 있는 함수를 뜻하기도 합니다.</aside>이렇게 컴포넌트로 생각하고 앱을 개발해 나아간다면, 코드 재사용이 용이 해집니다.실제 우리 예제에 어떻게 적용할 수 있는지. 확인해보겠습니다.
          • </aside>
          • 코드 재사용이란 어려운 용어는 아니고, 버튼 하나를 만들었을 때 이 버튼 코드를 여러 페이지에서 사용 가능하다는 뜻입니다.
          • <aside> 👉 버튼 하나가 컴포넌트가 될 수 있고, 버튼을 모아둔 영역이 컴포넌트가 될 수 있습니다.
          • 따라서 App.js를 App 컴포넌트라고도 부를 수 있습니다.
          • <aside> 👉 컴포넌트는 UI의 요소, 요소를 재사용 가능한 부분으로 조각내서 운영하는 기법입니다. 리액트 기반으로 만들어진 페이스북 웹사이트는 운영되는 컴포넌트가 수 만가지라고 합니다
          • [실습 ✍️ ] 메인화면 컴포넌트화 해보기그럼 다음 코드를 Card.js에 넣어주세요! 어디서 많이 본 코드입니다 그쵸?<aside> 🚧 마찬가지로 MainPage.js 화면을 확인하려면 App.js를 바꿔야 겠죠? return (<MainPage/>) 으로요!
            • [코드스니펫] Card.js
            • import React from "react" import {View,Text,Image,StyleSheet} from "react-native"; //비구조 할당 방식으로 넘긴 속성 데이터를 꺼내 사용함 export default function Card({content}) { return (<View style={styles.card}> <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>) } const styles = StyleSheet.create({ 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", } })
            <aside> 👉 맞습니다! MainPage.js에서 이미지위에 텍스트가 있던, 한 줄에 세 개씩 나열 되었던 그 카드 버튼입니다!.</aside>
            • [코드스니펫] MainPage.js
            • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; **import Card from '../components/Card';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (**<Card content={content} key={i}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
            <aside> 👉 MainPage.js에 있던 코드들을 Card.js로 옮겼습니다. 이렇게 옮기게 되면, 추후에 MainPage.js가 아닌 다른 페이지(페이지 또한 컴포넌트로 볼 수 있습니다) 또는 컴포넌트에서 Card.js를 사용할 수 있습니다.</aside>
            • MainPage.js
            • Card.js
            <aside> 👉 미리 말씀드리자면 MainPage.js에서 Card.js로 데이터를 넘기고 있는 모습입니다! 이를 속성을 넘긴다! 라고 보통 일컫는데 이는 다음 챕터에서 더 자세히 다루도록 하겠습니다!
          • </aside>
          • 그런데 약간 생소한 부분이 있습니다 다음 두 부분인데요!
          • 그럼 MainPage.js는 다음과 같이 변경해주세요
          • </aside>
          • </aside>
          • <aside> 👉 Components 폴더 안에 Card.js라는 파일을 만듭니다. 보통 컴포넌트들은 components 폴더에 모아두면서 관리하곤 합니다.
          • 속성(Props)<Text> 태그엔 numberOfLines이란 속성이 있었습니다. 말줌임표 효과를 주었죠! <Image> 태그엔 resizeMode란 속성이 있었습니다. 이미지가 영역을 차지하는 방식을 나타냈죠.</aside>예를 들어 Card를 사용하는 곳에서<Card image={'이미지 주소'}/> 라고 속성을 써 넣으면, 이 <Card/>는 image라는 속성이름에 {'이미지 주소'} 값을 을 갖게 됩니다. 즉 키와 벨류의 값을 갖게 됩니다 근데 이게 무슨 의미가 있을까요?</aside>
          • 의미가 있습니다. 속성을 부여받은 컴포넌트에서 해당 속성 값을 받아서 사용 할 수 있거든요! 다음과 같이 말이죠!
          • <aside> 👉 우리가 만든 <Card/> 컴포넌트에도 속성이 존재합니다. 우리가 부여 하면 부여하는 대로 속성이 되는 겁니다.
          • 이 모든 속성들은 공식 문서에 나와 있고, 해당 엘리먼트들이 태어날 때부터 가지고 있는 속성이였습니다.
          • <aside> 👉 속성은 쉽게 생각해서 컴포넌트에 데이터를 전달한다는 것이다. 그 전달 모습은 키와 벨류의 형태입니다.
          • [확인해보기👀 ]</aside>
            • MainPage.js 에서의 Card.js 컴포넌트
            <aside> 👉 어렵지 않은 규칙이 숨어 있습니다!
            1. 컴포넌트에 속성(데이터)을 부여해줘서 전달할땐, 키와 벨류(content={content}) 형태로 전달해줘야 할 것
            2. 컴포넌트를 반복문 돌릴땐, 컴포넌트마다 고유하다는 것을 표현하기 위해, map에서 나오는 인덱스(i)를 key = {i} 속성 전달 형태로 꼭 넣을것! 입니다.
            </aside>
            • Card.js 에서의 속성 값 내려 받기
            <aside> 👉 오랜만에 복습겸 나온 비구조 할당 방식 의 모습입니다. MainPage.js에서 넘겨준 속성은 실제 받게되는 컴포넌트에서 정말 딕셔너리 데이터를 받았다! 라고 생각하면 됩니다.이 방식으로 넘겨준 키값을 {키,키,키} 비구조 할당 방식으로 바로 꺼내서 사용하면 됩니다!
          • </aside>
          • 때문에, 비구조 할당 방식이 뭐였죠? 딕셔너리에서 키값을 바로 취해서 변수로써 함수안에서 즉시 사용할 수 있는 방식이였죠?
          • <aside> 👉 MainPage.js에서 방금전 컴포넌트화! 했던 Card.js를 우린 이렇게 사용했습니다.
          • 상태(State, useState)<aside> 👉 컴포넌트마다 데이터를 보유하고 관리 할 수 있습니다. 데이터라고 불러도 되지만, 리액트에서는 컴포넌트에서 보유/관리 되는 데이터를 **상태**라 부릅니다.사용방법은 실습을 통해 알아보도록 하겠습니다!
          • </aside>
          • 리액트에서 상태(state)는 리액트 라이브러리에서 제공해주는 useState로 생성하고 setState 함수로 정/변경 할 수 있습니다.
          • useState, 그렇다면 왜 중요할까?여러분이 만드는 화면은 데이터에 따라 변경됩니다 또 아무 데이터가 아니라 이 상태(state)로 관리되는 데이터가 변경되면 화면이 바뀝니다.</aside><aside> 👉 당시 개발자들끼리 이 상태와 컴포넌트의 관계를 공식으로 표현한 글을 보고 대단하다!! 했던 기억이 있는데, 여러분들은 어떻게 느끼실지 궁금하네요 ㅎㅎ무슨말인지 아직 잘 와닿지 않으시죠? 다른건 잘 와닿지 않아도 state는 컴포넌트에서 관리되는 상태만 기억하시면 충분합니다 이 개념은 이번 강의에서 개념을 다 배운 뒤, 다음 6 강에서 실습 하며 직접 확인할 수 있습니다
          • </aside>
          • 저 공식은 즉, 사용자 화면(UI)는 컴포넌트(component)에 어떤 데이터(state)가 주입되고 변경되냐에 따라 변화된다를 뜻합니다
          • UI = component(state)
          • 모르셔도 되지만 꽤 유의미한 공식이 있습니다
          • <aside> 👉 리액트는 특이한 점이 있습니다.
          • 화면이 그려진다음 가장 먼저 실행되는 함수, useEffect화면이 그려진 다음 가장 먼저 실행되는 함수로 사용방법이 간단합니다 형식만 알고 있으면 충분합니다
            **useEffect(()=>{**
            
            	...화면이 그려진 다음 가장 먼저 실행되야 할 코드 작성 공간
            
            **},[])**
            
            <aside> 👉 useEffect(()=>{ },[])</aside>
          • 이 안에서, 화면이 그려진다음 실행시키고 싶은 함수를 작성한다면 가장먼저 실행이 됩니다. useEffect 또한 어떤 역할을 하는지만 알고! 바로 실습을 통해 확인해보도록 하겠습니다
          • </aside>
          • <aside> 👉 useEffect는 너무 간단하고 유용하게 쓰이는 리액트 기본 제공 함수입니다.
          • useEffect, 그렇다면 왜 중요할까?
            1. 화면이 그려진다
            2. useEffect가 데이터를 준비한다
            3. 상태 데이터가 업데이트 되었으니 화면이 다시 그려진다
            화면이 그려진 다음, 서버에게 필요한 데이터를 요청하여 받은 후, 화면을 다시그릴때 주로 사용되는데요! 아주 일반적인 패턴이므로 여러분들도 금방 이해하고 익숙해지게 됩니다!
          • </aside>
          • <aside> 👉 보통 useEffect는 데이터를 준비할 때 사용합니다 데이터를 준비한다는 것은, 데이터를 서버로부터 혹은 어디선가로부터 받은 후 상태(state)에 반영한다는 것을 뜻합니다. 이런순서로 말이죠
          • [실습✍️ ] data.json을 상태(state)에 넣어서 관리해보기복습하실때 주석도 자세히 살펴보세요!
            • [코드스니펫] MainPage.js
            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //useState 사용법 //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수 //setState는 state를 변경시킬때 사용해야하는 함수 //모두 다 useState가 선물해줌 //useState()안에 전달되는 값은 state 초기값 const [state,setState] = useState([]) //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수 //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음 useEffect(()=>{ setState(data) },[]) // let tip = data.tip; //data.json 데이터는 state에 담기므로 상태에서 꺼내옴 let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
            <aside> 👉 그러나 실행을 하면 다음과 같은 오류 화면이 나옵니다
            • 오류화면
            <aside> 👉 그 이유는 이런 절차로 발생됩니다.
            1. 화면이 그려진다
            2. useEffect가 데이터를 state에 useState를 이용하여 업데이트한다
            3. 상태(state)가 변경되었으니 화면이 다시 그려진다
            여기서 1번에서 오류가 난겁니다. 화면이 가장 먼저 그려질때, 우리가 Card에 데이터를 넘기기위해 어떤일을 했나요?이런 오류는 우리가 지금 배운 개념들, 상태, 컴포넌트, useEffect를 이용하여 로딩화면을 만들어줌으로써 해결이 가능합니다!
          • </aside>
          • 처음부터 데이터가 없어서 , 꺼내올수 없는 오류
          • <aside> 👉 상태(state)에서 tip 키 값에 접근하여 데이터를 꺼내 map 즉, 반복문을 돌리려했습니다. 그런데 state에 데이터가 처음부터 있었나요? 없었죠? useEffect를 거쳐야 state에 데이터가 담기는데 아직 없는 상태라 오류가 나는 겁니다.
          • </aside>
          • </aside>
          • </aside>
          • <aside> 👉 지금 꿀팁을 data.json 파일에서 꺼내 바로 JSX 문법에서 사용하고 있습니다. 이를 다음과 같이 상태에 저장한 다음 사용하도록 변경해보도록 하겠습니다.
          04. [앱 필수 기초지식 응용] 로딩화면, 카테고리 기능, 상태 바
          • 앱 운영에 필요한 로딩화면이때 우린 로딩화면을 만들어줌으로써 쉽게 해결이 가능합니다. 컴포넌트 폴더에 Loading.js 파일을 만들어주고 다음 코드를 넣어주세요
            • [코드스니펫] Loading.js
            • import React from 'react'; import {View,Text,StyleSheet} from 'react-native'; export default function Loading(){ return(<View style={styles.container}><Text style={styles.title}>준비중입니다...</Text></View>) } const styles = StyleSheet.create({ container: { //앱의 배경 색 flex:1, justifyContent:'center', alignItems:'center', backgroundColor: '#fdc453', }, title: { fontSize:20, fontWeight:'700' } })
            <aside> 👉 그리고 MainPage.js 다음 코드 스니펫을 넣어주세요
            • [코드스니펫] MainPage.js
            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 const [state,setState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. **const [ready,setReady] = useState(true)** useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 **setTimeout(()=>{ setState(data) setReady(false) },1000)** },[]) // let tip = data.tip; let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" **//처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐)** return **ready ? <Loading/>** : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
            • 적용 모습
            <aside> 👉 ready 라는 새로운 상태값이 추가되었습니다. 즉, 상태값은 컴포넌트안에 여러개가 될 수 있습니다.즉, 이런 순서로 진행된거에요!
            1. ready 값이 true이므로 return 구문에서 ? 물음표 바로 뒤의 Loading 컴포넌트가 화면에 그려짐
            2. 화면이 그려지고 난다음, 1초 이따가 상태값들이 채워지고 변경됨
            3. ready 상태 값이 false가 됨
            4. 상태값이 변경되었으므로 화면이 다시 그려짐
            5. ready 값이 false 이므로 return 구문에서 : 콜론 뒤의 MainPage 컴포넌트가 화면에 그려짐
            차근차근 순서따라 반복해서 보면 별거아닙니다. 정해진 규칙에 따라 우린 코드를 적용했고 정해진 규칙에 따라 코드가 실행되고 있는 거에요!
          • </aside>
          • useEffect안에 setTimeout이라는 함수가 존재합니다. , 뒤에 있는 숫자 (1000 === 1초) 만큼, 지연됐다가 안에 있는 코드가 실행되는 지연 함수입니다.
          • </aside>
          • </aside>
          • <aside> 👉 5장에서 오류가 났던 것 처럼 화면이 그려질때 준비된 데이터가 없다!? 이러한 상황은 아주 빈번합니다.
          • 카테고리 버튼상태값이 바뀌면 화면이 다시 그려진다! 라는 개념을 염두하고 한번 만들어보겠습니다!
          • </aside>
          • <aside> 👉 우리에게 이런 버튼이 있었습니다. 카테고리 버튼인데요! 이 버튼은 장식이아니라, 실제 누르면 하단의 데이터가 카테고리에 맞게 다시 정렬되는 기능이 의도되어 있습니다.
          • [실습✍️ ] 카테고리 기능 넣기그리곤 함수가 필요합니다. 우리가 만든, 생활 재테크 반려견 버튼에 연결할 함수인데요!? 이 함수는 카테고리에 따라 카테고리 상태데이터를 새롭게 구성해주는 기능을 합니다.
            • [코드스니펫] MainPage.js
            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 **const [state,setState] = useState([])** //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 **const [cateState,setCateState] = useState([])** //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 **let tip = data.tip; setState(tip) setCateState(tip) setReady(false)** },1000) },[]) **const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } }** let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> **<TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>** <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, **middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 },** 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" }, **middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" },** cardContainer: { marginTop:10, marginLeft:10 }, });
            • 제태크를 눌렀을때의 모습
          • </aside>
          • <aside> 👉 카테고리 기능을 위해선 먼저 카테고리 상태가 필요합니다. 카테고리에 맞는 꿀팁들을 지속적으로 저장하고 관리할 상태 그릇이죠!
          • Expo에서 제공해주는 앱다운 앱 기능들</aside>
          • <aside> 💡 Expo SDK. 일명 Expo에서 제공해주는 앱 기능 도구들을 개발할 때 여기서 확인 할 수 있습니다. (링크) 필요한 기능들이 있는지 목록을 쭉 보고, 원하는 기능을 선택해서 적용하면 끝!
          • 상태 바란?</aside><aside> 💡 배터리가 충분한지, 몇시인지 매일 보시죠? 이부분도 우리가 처리를 할 수 있습니다.
          • </aside>
          • <aside> 💡 앱이 앱에 따라 모바일 맨 위 상태 바가 변하는 앱이 있습니다. 상태바란 이런 겁니다. 여러분들 매일 같이 보는 부분이에요!
          • StatusBar
            • 본격적으로 라이브러리 설치 시작!</aside><aside> 👉 터미널 우측 버튼중에 분할이란 버튼이 있습니다.</aside>
            • 여러분들 터미널 왼편은 서버를 켜고 끄고를 담당하고(expo start) 터미널 우측은 필요한 라이브러리들을 설치할 때 명령어를 치는 장소로 분할해서 사용하면 편리합니다!
            • <aside> 👉 이제 본격적으로 라이브러리들을 설치합니다. vscode에서 터미널을 이렇게 변경해주세요
            • [코드스니펫] Expo 상태 바 설치
            • expo install expo-status-bar
            • [실습 ✍️] 적용적용 모습을 살펴볼까요?
              • [코드스니펫] MainPage에 StatusBar 설치
              • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; **import { StatusBar } from 'expo-status-bar';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> **<StatusBar style="black" />** <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
              <aside> 💡 style이 light일 때와, black일 때가 다릅니다. 우린 앱 화면 배경색을 검은색으로 설정했기 떄문에, 상태바를 black으로 하면 보이지 않습니다.Status Bar 공식문서
              • black 일때
              • light 일때
            • </aside>
            • 상태 바 속성은 공식문서에 다양하게 존재하니, 살펴보면서 앱에 적합한 상태 바를 적용해보세요!
            • </aside>
            • <aside> 💡 컴포넌트마다 다르게 적용할 수도 있고, 앱 전체에 공통적으로 적용할 수 있습니다. StatusBar를 컴포넌트 각각 다르게 둘 수 있습니다 우린 MainPage.js에 달아볼거에요!
          05**. [앱** 페이지 적용**] 네비게이터 사용하기**
          • 네비게이션이란?<aside> 👉 앱에 페이지 개념을 입혀주고! 웹 사이트를 이용하듯, 앱에서 여러분들이 만든 컴포넌트들을 페이지화 시켜주고, 해당 페이지끼리 이동을 가능하게 해주는 라이브러리입니다가령 다음 화면과 같이, 앱 하단에 탭 버튼을 두고 해당 버튼을 누르면 연결되어 있는 페이지로 바로바로 이동할 수 있게 해주는 기능(열 앱에서 많이 보셨죠?)도 가지고 있습니다.</aside>
          • 따라서 이번시간에 같이 공부한 다음 틈틈히 다른 기능들도 적용해보세요!
          • 이번 강의에선 앱 내의 페이지 구성 및 이동까지 직접 적용해볼 텐데요!, 이 라이브러리는 다양한 기능들을 가지고 있습니다.
          • <aside> 💡 결국 이 라이브러리도 Expo에서 지원하고 있는 도구로써, 앱을 만들기 위해 사용 중인 Expo와 궁합이 아주 잘 맞습니다.
          • </aside>
          • <aside> 💡 물론 이것 또한 쉽게 페이징을 도와주는 외부 라이브러리를 가져다가 사용합니다. react-navigation 공식문서 보러가기 —> (링크)
          • </aside>
          • 현재 갖추고 있는 페이지 구성 확인!</aside>
          • <aside> 💡 페이지 구성은 보통 pages 폴더 안에 생성해둔 컴포넌트로 구성합니다. 우리는 이미, MainPage.js 메인 페이지 DetailPage.js 상세 화면 페이지 그리고 숙제로 만든 소개화면인 AboutPage.js 까지 총 3 페이지를 가지고 있습니다.
          • 기본 설치 코드우리에게 필요한 것이라고 했지만, 거의 대부분의 앱에 적용이 되어 있는 것들만 가져왔습니다.아래 명령어를 차례차례 터미널에 넣어 실행해주세요 가장 마지막줄은 꽤 깁니다. 이렇게 여러 라이브러리들을 띄어쓰기로 연결해서 한번에 설치 할 수도 있습니다.
            • [코드스니펫] 네비게이션 설치 코드
            • yarn add @react-navigation/native
            • [코드스니펫] 네비게이션 추가 설치코드
            • expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
          • </aside>
          • 아래 명령어들은 네비게이션을 사용하기 위해 필요한 기본 라이브러리들이구요! 우리가 곧 배울 스택 네비게이션와 탭 네비게이션 기능은 추가적으로 라이브러리를 또 설치 해줘야 합니다 😂
          • <aside> 💡 리액트 네이티브에서 페이지 네비게이션을 구현하기란 조금 까다롭습니다. 그래서 딱 우리에게 필요한 것만 가져와 설치 하고, 적용해 나갈 계획입니다.
          • 스택 네비게이션이란?</aside><aside> 💡 컴포넌트를 페이지화 시키는 스택 네비게이션은 다음과 같습니다.이렇게 만든 여러 페이지들을 책갈피 기능을 하는 스택 네비게이터에 모조리 등록시켜서, 언제든지 이 페이지 이동이 가능하게끔 해줍니다</aside>
          • 페이지는 Stack.Screen 이라 부르며 책갈피는 Stack.Navigator라 부릅니다
          • 우리가 페이지처럼 만든 컴포넌트를, 정말 페이지처럼 사용할 수 있게끔 페이지로 컴포넌트를 감싸 페이지로 만들어줍니다.
          • <aside> 💡 스택 네비게이션은 컴포넌트에 페이지 기능을 부여해주고 컴포넌트에서 컴포넌트로 이동, 즉 페이지 이동을 가능하게 해줍니다
          • createStackNavigator 사용해보기
            • [코드스니펫] 스택 네비게이터 설치 코드
            • yarn add @react-navigation/stack
            • [실습 ✍️] 적용하기</aside>
              • [코드스니펫] StackNavigator.js 스택 네비게이터
              • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
              <aside> 👉 워워 진정하세요! 위에서부터 차근차근 살펴볼거에요 🙂 언제나 우린 미니카 조립 설명서를 보면서 조립하듯, 공식 문서 내용 그대로 적용한다는 것 꼭 잊지마시고! 살펴보도록 하겠습니다.
              • 💻스택 네비게이터 코드 분석
                • 공식 문서 링크
                • 적용 순서 1) 사용 준비
                • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator();
                • 적용 순서 2) 기본 틀
                • //리액트의 모~든 파일은 컴포넌트라 생각하고 //페이지 기능을 해주는 모든 기능이 담겨 있는 컴포넌트를 만든다 생각하세요! const StackNavigator = () =>{ return ( /// 페이지 기능이 들어갈 곳 ) } export default StackNavigator;
                • 적용 순서 3) 스크린 옵션
                • //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
                • 적용 순서 4) 페이지 연결
                • {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/>
              <aside> 👉 컴포넌트를 페이지화 했고, 페이지를 이동할 수 있는 네이게이션도 준비가 됐다면, 우리는 최상단 컴포넌트 즉 App.js에 네비게이션 기능을 달아야 합니다.</aside>
              • [코드스니펫] App.js
              • import React from 'react'; //이제 모든 페이지 컴포넌트들이 끼워져있는 책갈피를 메인에 둘예정이므로 //컴포넌트를 더이상 불러오지 않아도 됩니다. // import MainPage from './pages/MainPage'; // import DetailPage from './pages/DetailPage'; import { StatusBar } from 'expo-status-bar'; //메인에 세팅할 네비게이션 도구들을 가져옵니다. import {NavigationContainer} from '@react-navigation/native'; import StackNavigator from './navigation/StackNavigator' export default function App() { console.disableYellowBox = true; return ( <NavigationContainer> <StatusBar style="black" /> <StackNavigator/> </NavigationContainer>); }
              • 스택 네비게이터 적용 후 MainPage 모습
            • 즉, 앱 가장 최상위 코드에 네비게이션을 다는겁니다 그래야 앱 어디서든 원하는 페이지 이동이 가능할테니까요!
            • </aside>
            • <aside> 👉 navigation 폴더 하나를 만들고 StackNavigator.js 파일을 만들어주세요 그리고 StackNavigator 안에 다음 코드를 넣어주세요
          • 페이지 헤더 수정</aside><aside> 💡 그 이유는 StackNavigator에서 현재 헤더에 대한 스타일과 페이지에 헤더의 제목까지 결정해주고 있기 때문입니다.
            • 스택 네비게이터의 헤더 스타일 부분 코드
            • <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTintColor: "#fff", headerBackTitleVisible: false }} > {/* name에 해당 하는 부분이 페이지의 타이틀이 됩니다.*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
            <aside> 💡 따라서 헤더 스타일을 적절히 바꾸기 위해 다음과 같이 옵션 부분의 코드를 수정해보도록 하겠습니다.
            • [코드스니펫] StackNavigator.js
            • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
            • [코드스니펫] MainPage.js 에서 title 삭제!
            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
            • StackNavigator 옵션 코드 수정 후 모습
            <aside> 💡 그래도 위에 페이지 제목으로 MainPage가 되는 모습은 뭔가 또 수정이 필요해보입니다. 이를 위해선 다음 스택 네비게이션 페이지 이동 강의에서 살펴보도록 하겠습니다
          • </aside>
          • </aside>
          • </aside>
          • <aside> 💡 그런데 현재 MainPage 화면은 다음과 같이 상단의 모습이 약간 이상해 보입니다.
          • 페이지 이동하기일단 페이지를 이동시키려면, 책갈피가 페이지들에게 부여해준 페이지 이동 기능을 사용해야 합니다.</aside><aside> 💡 일단 먼저 구조를 보여드렸습니다. 물론 사용을 해봐야 감이 오겠죠?같이 해보죠!
            • [실습 ✍️] 데이터 없이 페이지 이동하기
              • [코드스니펫] MainPage.js
              • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (**<Card content={content} key={i} navigation={navigation}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
              • [코드스니펫] Card.js
              • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 **export default function Card({content,navigation}){** return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage')}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
            • navigation.navigate("DetailPage")
            • [실습 ✍️] 데이터 가지고 페이지 이동하기</aside><aside> 💡 그럼 Card에서 DetailPage로 이동할 때, MainPage로 부터 넘겨받은 content도 넘겨볼까요?</aside>
              • [코드스니펫] Card.js
              • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function Card({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage',**content**)}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
              • [코드스니펫] DetailPage.js
              • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage({navigation,route}) { //초기 컴포넌트의 상태값을 설정 //state, setState 뿐 아니라 이름을 마음대로 지정할 수 있음! const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) //Card.js에서 navigation.navigate 함수를 쓸때 두번째 인자로 content를 넘겨줬죠? //content는 딕셔너리 그 자체였으므로 route.params에 고대~로 남겨옵니다. //즉, route.params 는 content죠! navigation.setOptions({ //setOptions로 페이지 타이틀도 지정 가능하고 title:route.params.title, //StackNavigator에서 작성했던 옵션을 다시 수정할 수도 있습니다. headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
              • 이동한 디테일 페이지 모습
              <aside> 💡 건네 받은 값을 꺼낼 땐, 다음과 같이 책갈피가 navigation과 추가적으로 건네준 route에서 꺼내 확인 할 수 있습니다.</aside>
              • route에 담겨져 오는 데이터 콘솔에서 직접 확인
              • DetailPage에서 상태값을 초기에 설정한 이유</aside><aside> 💡 그 이유는 컴포넌트가 화면에 그려지는 순서에 있습니다. 이건 지금 단계에선 조금 과한감이 있어서 간략히만 설명해보면 다음과 같습니다.
                1. DetailPage 컴포넌트가 useState에 들어 있는 
                   데이터 가지고 화면에 그려짐(return 함수실행)
                2. 화면에 다 그려진후, useEffect 함수 실행
                3. useEffect에서 상태값 변경 이벤트가 실행되면 변경된 데이터 가지고 다시 return 실행
                4. 변경된 데이터를 가지고 화면에 DetailPage가 다시 그려짐.
                
                <aside> 💡 곰곰히 생각해보면 결국, 리액트 네이티브에서 화면이 변경되는 시점은 컴포넌트의 상태값이 변경될 때니까요!또는! 우리가 한번 배웠떤 Loading.js를 이용하여 데이터가 준비가 되면 로딩 화면을 치우고, 본 화면을 보여준다던가!</aside>
              • 여러분들 마음껏 화면 처리를 해주시면 됩니다!
              • 이 개념은 어려울 수 있습니다 충분히! 따라서 처음에는, 어떠한 데이터를 보여주는 컴포넌트라면, 무조건 초기값을 의미없는 값이더라도 넣고 시작한다! 라고 생각하시면 편합니다!
              • </aside>
              • //초기 컴포넌트의 상태값을 설정 const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" })
              • <aside> 💡 DetailPage 초반에 우린 이렇게 상태값을 설정해놨었습니다. 그 이유가 뭘까요? 심지어 이 상태값을 초기에 설정안하면 오류가 발생합니다. tip엔 아무것도 없다며...
            • route.params 객체에 건네준 딕셔너리가 넘겨 있습니다!
            • 왜냐하면 지금은 어떠한 카드를 눌러도 상세 페이지에서 동일한 데이터를 보게 되잖아요? tip 데이터를 고정해놨으니까요!
            • navigation.navigate("Detail",**{** title**:** title **})**
            • <aside> 💡 버튼 카드에서 사용한 함수에 아래와 같이 두 번째 인자로 딕셔너리를 넘겨주면, 우리는 이동 한 페이지에서 넘겨준 데이터를 받을 수 있습니다.
          • </aside>
          • 그럼 이제 Card.js에 페이지 이동 기능을 달아보겠습니다. 코드를 실행 한 다음 페이지를 이동해 보면 익숙한 뒤로 가기 버튼도 보일거에요!
          • //navigation 객체가 가지고 있는 두 함수(setOptions와 navigate) //해당 페이지의 제목을 설정할 수 있음 navigation.setOptions({ title:'나만의 꿀팁' }) //Stack.screen에서 name 속성으로 정해준 이름을 지정해주면 해당 페이지로 이동하는 함수 navigation.navigate("DetailPage") //name 속성을 전달해주고, 두 번째 인자로 딕셔너리 데이터를 전달해주면, Detail 페이지에서 //두번째 인자로 전달된 딕셔너리 데이터를 route 딕셔너리로 로 받을 수 있음 navigation.navigate("DetailPage",**{ title: title }**) //전달받은 데이터를 받는 route 딕셔너리 //비구조 할당 방식으로 route에 params 객체 키로 연결되어 전달되는 데이터를 꺼내 사용 //navigate 함수로 전달되는 딕셔너리 데이터는 다음과 같은 모습이기 때문입니다. /* { route : { params :{ **title:title** } } } */ **const {** title**} = route.params;**
          • Stack.screen에 등록된 모든 페이지 컴포넌트들은 navigation 와 route 라는 딕셔너리(객체)를 속성으로 넘겨받아 사용할 수 있습니다. 이 두 딕셔너리는 다음과 같은 기능을 갖습니다.
          • <aside> 💡 Main 페이지(MainPage.js)에서 카드 버튼을 누르면(Card.js) 꿀팁 상세 페이지(DetailPage.js)로 이동합니다
          06**. [Expo 앱다운 앱기능] 공유 기능과 외부 링크 추가**
          • Share그런데 결과를 친구한테 공유하고 싶은 마음이 들수도 있겠네요 결과를 공유해주고, 앱다운로드 주소까지 전달해준다면 여러분이 만든 앱 홍보하기가 수월하겠죠?
          • </aside>
          • <aside> 👉 디테일 페이지까지 이동해서 도달했다면 여러분은 앱 개발자!
          • Share 적용해보기
            • 준비!
              • 설치 할 라이브러리 없이, react-native에서 기본적으로 제공해주는 공유 기능을 사용합니다.
              import { Share } from "react-native";
              
              • 그리고 우린 다음과 같은 공유 버튼을 디테일 페이지 하단에 생성하고 기능을 만들꺼에요! 한번 잠깐 영상을 멈추고 기능은 생각 말고 다음 버튼을 추가한 화면만 그려보세요! 복습 복습
              • 버튼 추가 디테일 페이지 모습
            • [실습 ✍️] 적용</aside>
              • [코드스니펫] DetailPage.js
              • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { **Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); }** return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}**>팁 공유하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:100, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
              • 공유하는 모습
            • <aside> 💡 share 함수를 만들고 거기에 react-native가 기본적으로 제공해주는 Share 함수를 사용하여 간단히 공유를 할 수 있습니다
            • 공유 모습
          • Linking이번엔 앱에서 외부 링크를 여는 방법에 대해 배워봅니다. 버튼을 누르면, 버튼에 연결 시킨 기능을 통해 외부 링크를 핸드폰에 있는 기본 브라우저로 열어봅니다.
          • </aside>
          • <aside> 👉 디테일 페이지에 있는 내용들이 사실 어디에서 가져온거라면? 출처를 남겨야 할겁니다. 그리고 그 출처로 바로가기 버튼정도도 있으면 여러분들이 만드는 다양한 플랫폼들을 서로 연결 시킬 수 있겠죠?
          • Linking 적용해보기
            • 준비!
              • DetailPage에 버튼을 다음과 같이 추가해주세요
              • 링크 버튼 추가 된 DetailPage 모습
              • expo 에서 제공해주는 도구를 설치 한다음, 해당 도구를 상단에 가져와 준비해야 합니다
              expo install expo-linking
              
              import * as Linking from 'expo-linking';
              
            • [실습 ✍️] 적용
              • [코드스니펫] DetailPage.js
              • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; import * as Linking from 'expo-linking'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); } const link = () => { Linking.openURL("<https://spartacodingclub.kr>") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}>팁 공유하기 link()}>외부 링크 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:90, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
          07**. 3주차 끝 & 숙제 설명**
            1. 어바웃 화면 페이지화 시키고 버튼 추가하기!
            <aside> 👉 지난번 숙제로 만든 AboutPage.js를 아직 우린 스택 네비게이터에 연결하지 않았습니다.추가적으로 외부 링크 걸기까지 배웠으니, 여러분 인스타나 블로그 주소 혹은 아무주소를 AboutPage 하단 버튼에 연결해주세요!
            • 만들 화면
            • 가이드
              1. 메인에 TouchableOpacity 버튼을 두고 navigate를 연결시키면 이동이 가능하겠죠?
              2. Stack.screen에 AboutPage.js를 연결시켜야합니다!
              3. 소개 페이지 상태바의 디테일함을 주목하세요! 흰색이네요 🙂
          • </aside>
          • 따라서 여러분이 직접! 스택네비게이터에 AboutPage.js를 달아보구요! 하단 메인페이지의 상단에 다음과 같은 버튼을 만들어서 누르면 AboutPage로 가게끔 해주세요!
            1. 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요
          • 가이드
              1. 찜 데이터 제공
              <aside> 💡 나만의 꿀 팁 앱에서 찜 페이지란 쉽게 말해 팁 리스트 중에 선택한 팁들을 모아보는 페이지입니다. 이런 찜 페이지 자주 보셨죠?
              • 즉, 다음 상태값으로 화면을 구성해주세요::: 리스트에 딕셔너리 두 개가 들어있는 데이터입니다.
              </aside>
              • [코드스니펫] 찜 상태 데이터
              • const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }])
            • 하지만 아직 데이터를 저장하고, 수정하고 등등의 데이터 관리 부분을 배우지 않았기 때문에 [코드스니펫] 찜 상태 데이터를 가지고 화면만 그려볼겁니다.
              1. 파일의 위치와 이름
              <aside> 💡 새로운 페이지를 만드는 중이니, 어디에 위치해야 할까요? 당연히 pages 폴더에 위치시켜야 겠죠?</aside>
            • 또한 파일이름은 우리 LikePage.js 로 만들어 봅시다! ZzimPage는 좀...
              1. 메인의 꿀팁찜 버튼을 누르면 찜 페이지로!
              <aside> 💡 혼자만의 큰그림이 있었습니다. 메인페이지에 꿀팁찜 버튼이란걸 강의 초반부터 만들어 놓았었어요! 이 버튼을 누르면 찜 페이지로 가게끔 해봅시다!
            • </aside>
              1. Card.js 를 복붙해서 찜 페이지에서만 사용하는 LikeCard.js 만들기
              <aside> 💡 찜 목록도 Card.js와 크게 다를건 없습니다. 기존 카드에 찜 삭제 버튼정도가 추가 되었다는 점?이 다르므로, Card.js 코드를 복붙해서 LikeCard.js를 만들어 봅시다</aside>
            • 그런데! LikeCard.js에선 TouchableOpacity로 감싸는게 아닌! View 태그로 카드 전체를 감싸주도록 바꿔주세요!
              1. 최종 폴더 & 파일 모습
          HW. 3주차 숙제 해설
          • 숙제 1: 어바웃 화면 페이지화 시키고 버튼 추가하기!
            • [코드스니펫] StackNavigator.js(숙제1)
            • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> </Stack.Navigator> ) } export default StackNavigator;
            • [코드스니펫] AboutPage.js(숙제1)
            • import React,{useEffect} from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' import { StatusBar } from 'expo-status-bar'; export default function AboutPage({navigation,route}){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" useEffect(()=>{ navigation.setOptions({ title:"소개 페이지", headerStyle: { backgroundColor: '#1F266A', shadowColor: "#1F266A", }, headerTintColor: "#fff", }) },[]) return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:20, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
            • [코드스니펫] MainPage.js(숙제1)
            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20, }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
          • 숙제 2: 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요!
            • [코드스니펫] StackNavigator.js(숙제2)
            • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; import LikePage from '../pages/LikePage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> <Stack.Screen name="LikePage" component={LikePage}/> </Stack.Navigator> ) } export default StackNavigator;
            • [코드스니펫] LikeCard.js(숙제2)
            • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function LikeCard({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <View style={styles.card}> <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> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
            • [코드스니펫] MainPage.js(숙제2)
            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> **<TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>** </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
            • [코드스니펫] LikePage.js(숙제2)
            • import React,{useState, useEffect} from 'react'; import {ScrollView, Text, StyleSheet} from 'react-native'; import LikeCard from '../components/LikeCard'; import Card from '../components/Card'; export default function LikePage({navigation,route}){ const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }]) useEffect(()=>{ navigation.setOptions({ title:'꿀팁 찜' }) }) return ( { tip.map((content,i)=>{ return() }) } ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#fff" } })01. 오늘 배울 것
              • 오늘 배울 내용이 두 가지는 앞으로도 계속~ 반복 숙달합니다 🙂
                1. 리액트 필수 지식 활용
                2. Expo 기능 사용
                3. 페이지 적용
                </aside>
              • 오늘은 앱다운 앱을 만드는 연습과 기술을 배울텐데요! 다음 순서대로 지금까지 배웠던 것을 복습 하면서 앱다운 앱 기능을 배우니 점점 더 여러분은 앱 개발에 숙달되는 모습을 볼 수 있습니다!
              • <aside> 👉 지난 시간까지 자바스크립트 기초체력을 다지고 JSX로 화면을 그리는 방법 까지 배워봤습니다.
              • [앱 필수 기초지식] 리액트 필수지식
                • 컴포넌트(Component)
                • 상태(State,useState)
                • 속성(Props)
                • useEffect
                이렇게 네 가지입니다. 단어가 조금 어려워 보이지만, 재미있고 간단한 개념들입니다. 이 개념들을 통해 앱 코드를 체계적으로 관리할 수 있고 화면안에서 데이터를 자유자재로 관리할 수도 있습니다.리액트 네이티브(React Native)는 리액트(React.js) 기반으로 만들어진 앱 개발 기술이기 때문이에요! 겁먹지마시고 차근차근 다루어봅시다.
              • </aside>
              • 최소한의 리액트 개념을 배우는 이유는
              • <aside> 👉 리액트 네이티브 앱을 만들기 위해서 알아야 하는 최소한의 리액트 개념을 배웁니다.
              • [Expo 앱다운 앱기능] 앱다운 앱을 위해 사용할 리액트 네이티브와 Expo 기능들이렇게 앱 다운 앱이 되기위한 기능들을 공식문서를 보고 하나하나 적용해보는 시간을 갖습니다. 이 연습을 거치면 여러분들은 공식문서를 보며 필요한 앱 기능들을 여러분들 앱에 붙여나갈 수 있습니다!
              • </aside>
              • <aside> 👉 앱이라고 하면 보통 현재 보고 있는 내용을 친구들한테 공유하거나, 앱 안에 있는 링크를 누르면 바로 해당 링크로 화면이 전환되는 기능들이 들어 있습니다. 또한 배터리를 볼 수 있는 상태바 스타일도 앱 전체적인 스타일에 맞게 변경할 수도 있어야 할겁니다.
              • [앱 페이지 적용] 앱에 페이지 기능 넣기!그래서 앱안에 여러 페이지를 두고 버튼을 눌러 이동할 수 있는 기능을 달아봅니다. 본격적으로 거의 온전한 앱의 모습을 띄게 됩니다!
              • </aside>
              • <aside> 👉 앱에 페이지가 하나라면 너무 단조롭고 볼게 없는 앱이 되겠죠?
              02. [함께해보기] 나만의 꿀팁 앱 상세 화면
              • [실습 ✍️ ] 나만의 꿀팁 상세 페이지가될 아래의 화면을 만들어보세요
              • 퀴즈 가이드
                • [현재 코드 상황]
                  • App.js
                  • import React from 'react' import MainPage from './pages/MainPage'; import AboutPage from './pages/AboutPage'; export default function App(){ // return (<MainPage/>) return (<AboutPage/>) }
                  • MainPage.js
                  • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { 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> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, 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", } });
                  • AboutPage.js
                  • import React from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' export default function AboutPage(){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:100, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
                  • 폴더구조
                • DetailPage.js를 pages 폴더에 만들어 넣기</aside>
                • <aside> 👉 저번 1주차 마무리 시간에, pages 폴더를 만들고 AboutPage.js 파일을 만들어 넣었던 기억이 나시나요? 그때랑 동일하게 pages 폴더안에 DetailPage.js 파일을 만들고 App.js에서 return 부분에 DetailPage.js를 넣어주세요 다음과 같이요!
                • 문제 디테일 화면을 위한 데이터딕셔너리 형태로 키값에 접근해서 값을 꺼내 사용해도 되고! 문자열들 그대로 복사해서 Text 태그에 넣어 사용해도 됩니다!
                  • [코드스니펫] 상세 화면에서 사용될 데이터
                  • const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }
                • </aside>
                • <aside> 👉 화면에 나타낼 꿀 팁은 다음 데이터를 사용하세요.
              • 함께 해보기</aside>
                • [코드스니펫] DetailPage.js 화면
                • import React from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage() { const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" } const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
              • <aside> 👉 DetailPage를 확인하려면 App.js를 좀 바꿔야 겠죠? 어떻게 바꿔야 할까요?
              03**.** [앱 필수 기초지식] 컴포넌트, 속성, 상태, useEffect
              • 공부 할 리액트 필수 지식<aside> 👉 다시 떠올리자면, 리액트 네이티브는 리액트(React.js) 라이브러리 기반으로 만들어진 프레임워크 입니다. 그렇기 때문에 기본적인 구조는 리액트를 닯아 있습니다.1) 컴포넌트(Component) : 정해진 엘리먼트들(요소)을 사용하여 만든 화면의 일부분 2) 상태(State) : 컴포넌트에서 데이터를 유지하고 관리하기 위한 유일한 방법 == 그냥 사용할 데이터! 3) 속성(Props) : 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하는 방식 == 그냥 데이터 전달! 4) useEffect : 화면에 컴포넌트가 그려지면 처음 실행해야 하는 함수들을 모아두는 곳</aside>
              • 네 가지가 있습니다. 워워 어렵지 않아요. 이것도 정해진 규칙과 개념일 뿐! 사용방법 숙지!만 하면 그만이랍니다!
              • 대표적으로
              • 컴포넌트(Component)</aside><aside> 👉 컴포넌트는 즉, 화면의 모든 부분입니다. 그리고 컴포넌트란 App.js의 큰 App 함수처럼, 코드 전체를 감싸고 있는 함수를 뜻하기도 합니다.</aside>이렇게 컴포넌트로 생각하고 앱을 개발해 나아간다면, 코드 재사용이 용이 해집니다.실제 우리 예제에 어떻게 적용할 수 있는지. 확인해보겠습니다.
              • </aside>
              • 코드 재사용이란 어려운 용어는 아니고, 버튼 하나를 만들었을 때 이 버튼 코드를 여러 페이지에서 사용 가능하다는 뜻입니다.
              • <aside> 👉 버튼 하나가 컴포넌트가 될 수 있고, 버튼을 모아둔 영역이 컴포넌트가 될 수 있습니다.
              • 따라서 App.js를 App 컴포넌트라고도 부를 수 있습니다.
              • <aside> 👉 컴포넌트는 UI의 요소, 요소를 재사용 가능한 부분으로 조각내서 운영하는 기법입니다. 리액트 기반으로 만들어진 페이스북 웹사이트는 운영되는 컴포넌트가 수 만가지라고 합니다
              • [실습 ✍️ ] 메인화면 컴포넌트화 해보기그럼 다음 코드를 Card.js에 넣어주세요! 어디서 많이 본 코드입니다 그쵸?<aside> 🚧 마찬가지로 MainPage.js 화면을 확인하려면 App.js를 바꿔야 겠죠? return (<MainPage/>) 으로요!
                • [코드스니펫] Card.js
                • import React from "react" import {View,Text,Image,StyleSheet} from "react-native"; //비구조 할당 방식으로 넘긴 속성 데이터를 꺼내 사용함 export default function Card({content}) { return (<View style={styles.card}> <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>) } const styles = StyleSheet.create({ 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", } })
                <aside> 👉 맞습니다! MainPage.js에서 이미지위에 텍스트가 있던, 한 줄에 세 개씩 나열 되었던 그 카드 버튼입니다!.</aside>
                • [코드스니펫] MainPage.js
                • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; **import Card from '../components/Card';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (**<Card content={content} key={i}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                <aside> 👉 MainPage.js에 있던 코드들을 Card.js로 옮겼습니다. 이렇게 옮기게 되면, 추후에 MainPage.js가 아닌 다른 페이지(페이지 또한 컴포넌트로 볼 수 있습니다) 또는 컴포넌트에서 Card.js를 사용할 수 있습니다.</aside>
                • MainPage.js
                • Card.js
                <aside> 👉 미리 말씀드리자면 MainPage.js에서 Card.js로 데이터를 넘기고 있는 모습입니다! 이를 속성을 넘긴다! 라고 보통 일컫는데 이는 다음 챕터에서 더 자세히 다루도록 하겠습니다!
              • </aside>
              • 그런데 약간 생소한 부분이 있습니다 다음 두 부분인데요!
              • 그럼 MainPage.js는 다음과 같이 변경해주세요
              • </aside>
              • </aside>
              • <aside> 👉 Components 폴더 안에 Card.js라는 파일을 만듭니다. 보통 컴포넌트들은 components 폴더에 모아두면서 관리하곤 합니다.
              • 속성(Props)<Text> 태그엔 numberOfLines이란 속성이 있었습니다. 말줌임표 효과를 주었죠! <Image> 태그엔 resizeMode란 속성이 있었습니다. 이미지가 영역을 차지하는 방식을 나타냈죠.</aside>예를 들어 Card를 사용하는 곳에서<Card image={'이미지 주소'}/> 라고 속성을 써 넣으면, 이 <Card/>는 image라는 속성이름에 {'이미지 주소'} 값을 을 갖게 됩니다. 즉 키와 벨류의 값을 갖게 됩니다 근데 이게 무슨 의미가 있을까요?</aside>
              • 의미가 있습니다. 속성을 부여받은 컴포넌트에서 해당 속성 값을 받아서 사용 할 수 있거든요! 다음과 같이 말이죠!
              • <aside> 👉 우리가 만든 <Card/> 컴포넌트에도 속성이 존재합니다. 우리가 부여 하면 부여하는 대로 속성이 되는 겁니다.
              • 이 모든 속성들은 공식 문서에 나와 있고, 해당 엘리먼트들이 태어날 때부터 가지고 있는 속성이였습니다.
              • <aside> 👉 속성은 쉽게 생각해서 컴포넌트에 데이터를 전달한다는 것이다. 그 전달 모습은 키와 벨류의 형태입니다.
              • [확인해보기👀 ]</aside>
                • MainPage.js 에서의 Card.js 컴포넌트
                <aside> 👉 어렵지 않은 규칙이 숨어 있습니다!
                1. 컴포넌트에 속성(데이터)을 부여해줘서 전달할땐, 키와 벨류(content={content}) 형태로 전달해줘야 할 것
                2. 컴포넌트를 반복문 돌릴땐, 컴포넌트마다 고유하다는 것을 표현하기 위해, map에서 나오는 인덱스(i)를 key = {i} 속성 전달 형태로 꼭 넣을것! 입니다.
                </aside>
                • Card.js 에서의 속성 값 내려 받기
                <aside> 👉 오랜만에 복습겸 나온 비구조 할당 방식 의 모습입니다. MainPage.js에서 넘겨준 속성은 실제 받게되는 컴포넌트에서 정말 딕셔너리 데이터를 받았다! 라고 생각하면 됩니다.이 방식으로 넘겨준 키값을 {키,키,키} 비구조 할당 방식으로 바로 꺼내서 사용하면 됩니다!
              • </aside>
              • 때문에, 비구조 할당 방식이 뭐였죠? 딕셔너리에서 키값을 바로 취해서 변수로써 함수안에서 즉시 사용할 수 있는 방식이였죠?
              • <aside> 👉 MainPage.js에서 방금전 컴포넌트화! 했던 Card.js를 우린 이렇게 사용했습니다.
              • 상태(State, useState)<aside> 👉 컴포넌트마다 데이터를 보유하고 관리 할 수 있습니다. 데이터라고 불러도 되지만, 리액트에서는 컴포넌트에서 보유/관리 되는 데이터를 **상태**라 부릅니다.사용방법은 실습을 통해 알아보도록 하겠습니다!
              • </aside>
              • 리액트에서 상태(state)는 리액트 라이브러리에서 제공해주는 useState로 생성하고 setState 함수로 정/변경 할 수 있습니다.
              • useState, 그렇다면 왜 중요할까?여러분이 만드는 화면은 데이터에 따라 변경됩니다 또 아무 데이터가 아니라 이 상태(state)로 관리되는 데이터가 변경되면 화면이 바뀝니다.</aside><aside> 👉 당시 개발자들끼리 이 상태와 컴포넌트의 관계를 공식으로 표현한 글을 보고 대단하다!! 했던 기억이 있는데, 여러분들은 어떻게 느끼실지 궁금하네요 ㅎㅎ무슨말인지 아직 잘 와닿지 않으시죠? 다른건 잘 와닿지 않아도 state는 컴포넌트에서 관리되는 상태만 기억하시면 충분합니다 이 개념은 이번 강의에서 개념을 다 배운 뒤, 다음 6 강에서 실습 하며 직접 확인할 수 있습니다
              • </aside>
              • 저 공식은 즉, 사용자 화면(UI)는 컴포넌트(component)에 어떤 데이터(state)가 주입되고 변경되냐에 따라 변화된다를 뜻합니다
              • UI = component(state)
              • 모르셔도 되지만 꽤 유의미한 공식이 있습니다
              • <aside> 👉 리액트는 특이한 점이 있습니다.
              • 화면이 그려진다음 가장 먼저 실행되는 함수, useEffect화면이 그려진 다음 가장 먼저 실행되는 함수로 사용방법이 간단합니다 형식만 알고 있으면 충분합니다
                **useEffect(()=>{**
                
                	...화면이 그려진 다음 가장 먼저 실행되야 할 코드 작성 공간
                
                **},[])**
                
                <aside> 👉 useEffect(()=>{ },[])</aside>
              • 이 안에서, 화면이 그려진다음 실행시키고 싶은 함수를 작성한다면 가장먼저 실행이 됩니다. useEffect 또한 어떤 역할을 하는지만 알고! 바로 실습을 통해 확인해보도록 하겠습니다
              • </aside>
              • <aside> 👉 useEffect는 너무 간단하고 유용하게 쓰이는 리액트 기본 제공 함수입니다.
              • useEffect, 그렇다면 왜 중요할까?
                1. 화면이 그려진다
                2. useEffect가 데이터를 준비한다
                3. 상태 데이터가 업데이트 되었으니 화면이 다시 그려진다
                화면이 그려진 다음, 서버에게 필요한 데이터를 요청하여 받은 후, 화면을 다시그릴때 주로 사용되는데요! 아주 일반적인 패턴이므로 여러분들도 금방 이해하고 익숙해지게 됩니다!
              • </aside>
              • <aside> 👉 보통 useEffect는 데이터를 준비할 때 사용합니다 데이터를 준비한다는 것은, 데이터를 서버로부터 혹은 어디선가로부터 받은 후 상태(state)에 반영한다는 것을 뜻합니다. 이런순서로 말이죠
              • [실습✍️ ] data.json을 상태(state)에 넣어서 관리해보기복습하실때 주석도 자세히 살펴보세요!
                • [코드스니펫] MainPage.js
                • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //useState 사용법 //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수 //setState는 state를 변경시킬때 사용해야하는 함수 //모두 다 useState가 선물해줌 //useState()안에 전달되는 값은 state 초기값 const [state,setState] = useState([]) //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수 //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음 useEffect(()=>{ setState(data) },[]) // let tip = data.tip; //data.json 데이터는 state에 담기므로 상태에서 꺼내옴 let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                <aside> 👉 그러나 실행을 하면 다음과 같은 오류 화면이 나옵니다
                • 오류화면
                <aside> 👉 그 이유는 이런 절차로 발생됩니다.
                1. 화면이 그려진다
                2. useEffect가 데이터를 state에 useState를 이용하여 업데이트한다
                3. 상태(state)가 변경되었으니 화면이 다시 그려진다
                여기서 1번에서 오류가 난겁니다. 화면이 가장 먼저 그려질때, 우리가 Card에 데이터를 넘기기위해 어떤일을 했나요?이런 오류는 우리가 지금 배운 개념들, 상태, 컴포넌트, useEffect를 이용하여 로딩화면을 만들어줌으로써 해결이 가능합니다!
              • </aside>
              • 처음부터 데이터가 없어서 , 꺼내올수 없는 오류
              • <aside> 👉 상태(state)에서 tip 키 값에 접근하여 데이터를 꺼내 map 즉, 반복문을 돌리려했습니다. 그런데 state에 데이터가 처음부터 있었나요? 없었죠? useEffect를 거쳐야 state에 데이터가 담기는데 아직 없는 상태라 오류가 나는 겁니다.
              • </aside>
              • </aside>
              • </aside>
              • <aside> 👉 지금 꿀팁을 data.json 파일에서 꺼내 바로 JSX 문법에서 사용하고 있습니다. 이를 다음과 같이 상태에 저장한 다음 사용하도록 변경해보도록 하겠습니다.
              04. [앱 필수 기초지식 응용] 로딩화면, 카테고리 기능, 상태 바
              • 앱 운영에 필요한 로딩화면이때 우린 로딩화면을 만들어줌으로써 쉽게 해결이 가능합니다. 컴포넌트 폴더에 Loading.js 파일을 만들어주고 다음 코드를 넣어주세요
                • [코드스니펫] Loading.js
                • import React from 'react'; import {View,Text,StyleSheet} from 'react-native'; export default function Loading(){ return(<View style={styles.container}><Text style={styles.title}>준비중입니다...</Text></View>) } const styles = StyleSheet.create({ container: { //앱의 배경 색 flex:1, justifyContent:'center', alignItems:'center', backgroundColor: '#fdc453', }, title: { fontSize:20, fontWeight:'700' } })
                <aside> 👉 그리고 MainPage.js 다음 코드 스니펫을 넣어주세요
                • [코드스니펫] MainPage.js
                • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 const [state,setState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. **const [ready,setReady] = useState(true)** useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 **setTimeout(()=>{ setState(data) setReady(false) },1000)** },[]) // let tip = data.tip; let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" **//처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐)** return **ready ? <Loading/>** : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                • 적용 모습
                <aside> 👉 ready 라는 새로운 상태값이 추가되었습니다. 즉, 상태값은 컴포넌트안에 여러개가 될 수 있습니다.즉, 이런 순서로 진행된거에요!
                1. ready 값이 true이므로 return 구문에서 ? 물음표 바로 뒤의 Loading 컴포넌트가 화면에 그려짐
                2. 화면이 그려지고 난다음, 1초 이따가 상태값들이 채워지고 변경됨
                3. ready 상태 값이 false가 됨
                4. 상태값이 변경되었으므로 화면이 다시 그려짐
                5. ready 값이 false 이므로 return 구문에서 : 콜론 뒤의 MainPage 컴포넌트가 화면에 그려짐
                차근차근 순서따라 반복해서 보면 별거아닙니다. 정해진 규칙에 따라 우린 코드를 적용했고 정해진 규칙에 따라 코드가 실행되고 있는 거에요!
              • </aside>
              • useEffect안에 setTimeout이라는 함수가 존재합니다. , 뒤에 있는 숫자 (1000 === 1초) 만큼, 지연됐다가 안에 있는 코드가 실행되는 지연 함수입니다.
              • </aside>
              • </aside>
              • <aside> 👉 5장에서 오류가 났던 것 처럼 화면이 그려질때 준비된 데이터가 없다!? 이러한 상황은 아주 빈번합니다.
              • 카테고리 버튼상태값이 바뀌면 화면이 다시 그려진다! 라는 개념을 염두하고 한번 만들어보겠습니다!
              • </aside>
              • <aside> 👉 우리에게 이런 버튼이 있었습니다. 카테고리 버튼인데요! 이 버튼은 장식이아니라, 실제 누르면 하단의 데이터가 카테고리에 맞게 다시 정렬되는 기능이 의도되어 있습니다.
              • [실습✍️ ] 카테고리 기능 넣기그리곤 함수가 필요합니다. 우리가 만든, 생활 재테크 반려견 버튼에 연결할 함수인데요!? 이 함수는 카테고리에 따라 카테고리 상태데이터를 새롭게 구성해주는 기능을 합니다.
                • [코드스니펫] MainPage.js
                • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 **const [state,setState] = useState([])** //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 **const [cateState,setCateState] = useState([])** //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 **let tip = data.tip; setState(tip) setCateState(tip) setReady(false)** },1000) },[]) **const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } }** let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> **<TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>** <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, **middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 },** 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" }, **middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" },** cardContainer: { marginTop:10, marginLeft:10 }, });
                • 제태크를 눌렀을때의 모습
              • </aside>
              • <aside> 👉 카테고리 기능을 위해선 먼저 카테고리 상태가 필요합니다. 카테고리에 맞는 꿀팁들을 지속적으로 저장하고 관리할 상태 그릇이죠!
              • Expo에서 제공해주는 앱다운 앱 기능들</aside>
              • <aside> 💡 Expo SDK. 일명 Expo에서 제공해주는 앱 기능 도구들을 개발할 때 여기서 확인 할 수 있습니다. (링크) 필요한 기능들이 있는지 목록을 쭉 보고, 원하는 기능을 선택해서 적용하면 끝!
              • 상태 바란?</aside><aside> 💡 배터리가 충분한지, 몇시인지 매일 보시죠? 이부분도 우리가 처리를 할 수 있습니다.
              • </aside>
              • <aside> 💡 앱이 앱에 따라 모바일 맨 위 상태 바가 변하는 앱이 있습니다. 상태바란 이런 겁니다. 여러분들 매일 같이 보는 부분이에요!
              • StatusBar
                • 본격적으로 라이브러리 설치 시작!</aside><aside> 👉 터미널 우측 버튼중에 분할이란 버튼이 있습니다.</aside>
                • 여러분들 터미널 왼편은 서버를 켜고 끄고를 담당하고(expo start) 터미널 우측은 필요한 라이브러리들을 설치할 때 명령어를 치는 장소로 분할해서 사용하면 편리합니다!
                • <aside> 👉 이제 본격적으로 라이브러리들을 설치합니다. vscode에서 터미널을 이렇게 변경해주세요
                • [코드스니펫] Expo 상태 바 설치
                • expo install expo-status-bar
                • [실습 ✍️] 적용적용 모습을 살펴볼까요?
                  • [코드스니펫] MainPage에 StatusBar 설치
                  • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; **import { StatusBar } from 'expo-status-bar';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> **<StatusBar style="black" />** <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                  <aside> 💡 style이 light일 때와, black일 때가 다릅니다. 우린 앱 화면 배경색을 검은색으로 설정했기 떄문에, 상태바를 black으로 하면 보이지 않습니다.Status Bar 공식문서
                  • black 일때
                  • light 일때
                • </aside>
                • 상태 바 속성은 공식문서에 다양하게 존재하니, 살펴보면서 앱에 적합한 상태 바를 적용해보세요!
                • </aside>
                • <aside> 💡 컴포넌트마다 다르게 적용할 수도 있고, 앱 전체에 공통적으로 적용할 수 있습니다. StatusBar를 컴포넌트 각각 다르게 둘 수 있습니다 우린 MainPage.js에 달아볼거에요!
              05**. [앱** 페이지 적용**] 네비게이터 사용하기**
              • 네비게이션이란?<aside> 👉 앱에 페이지 개념을 입혀주고! 웹 사이트를 이용하듯, 앱에서 여러분들이 만든 컴포넌트들을 페이지화 시켜주고, 해당 페이지끼리 이동을 가능하게 해주는 라이브러리입니다가령 다음 화면과 같이, 앱 하단에 탭 버튼을 두고 해당 버튼을 누르면 연결되어 있는 페이지로 바로바로 이동할 수 있게 해주는 기능(열 앱에서 많이 보셨죠?)도 가지고 있습니다.</aside>
              • 따라서 이번시간에 같이 공부한 다음 틈틈히 다른 기능들도 적용해보세요!
              • 이번 강의에선 앱 내의 페이지 구성 및 이동까지 직접 적용해볼 텐데요!, 이 라이브러리는 다양한 기능들을 가지고 있습니다.
              • <aside> 💡 결국 이 라이브러리도 Expo에서 지원하고 있는 도구로써, 앱을 만들기 위해 사용 중인 Expo와 궁합이 아주 잘 맞습니다.
              • </aside>
              • <aside> 💡 물론 이것 또한 쉽게 페이징을 도와주는 외부 라이브러리를 가져다가 사용합니다. react-navigation 공식문서 보러가기 —> (링크)
              • </aside>
              • 현재 갖추고 있는 페이지 구성 확인!</aside>
              • <aside> 💡 페이지 구성은 보통 pages 폴더 안에 생성해둔 컴포넌트로 구성합니다. 우리는 이미, MainPage.js 메인 페이지 DetailPage.js 상세 화면 페이지 그리고 숙제로 만든 소개화면인 AboutPage.js 까지 총 3 페이지를 가지고 있습니다.
              • 기본 설치 코드우리에게 필요한 것이라고 했지만, 거의 대부분의 앱에 적용이 되어 있는 것들만 가져왔습니다.아래 명령어를 차례차례 터미널에 넣어 실행해주세요 가장 마지막줄은 꽤 깁니다. 이렇게 여러 라이브러리들을 띄어쓰기로 연결해서 한번에 설치 할 수도 있습니다.
                • [코드스니펫] 네비게이션 설치 코드
                • yarn add @react-navigation/native
                • [코드스니펫] 네비게이션 추가 설치코드
                • expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
              • </aside>
              • 아래 명령어들은 네비게이션을 사용하기 위해 필요한 기본 라이브러리들이구요! 우리가 곧 배울 스택 네비게이션와 탭 네비게이션 기능은 추가적으로 라이브러리를 또 설치 해줘야 합니다 😂
              • <aside> 💡 리액트 네이티브에서 페이지 네비게이션을 구현하기란 조금 까다롭습니다. 그래서 딱 우리에게 필요한 것만 가져와 설치 하고, 적용해 나갈 계획입니다.
              • 스택 네비게이션이란?</aside><aside> 💡 컴포넌트를 페이지화 시키는 스택 네비게이션은 다음과 같습니다.이렇게 만든 여러 페이지들을 책갈피 기능을 하는 스택 네비게이터에 모조리 등록시켜서, 언제든지 이 페이지 이동이 가능하게끔 해줍니다</aside>
              • 페이지는 Stack.Screen 이라 부르며 책갈피는 Stack.Navigator라 부릅니다
              • 우리가 페이지처럼 만든 컴포넌트를, 정말 페이지처럼 사용할 수 있게끔 페이지로 컴포넌트를 감싸 페이지로 만들어줍니다.
              • <aside> 💡 스택 네비게이션은 컴포넌트에 페이지 기능을 부여해주고 컴포넌트에서 컴포넌트로 이동, 즉 페이지 이동을 가능하게 해줍니다
              • createStackNavigator 사용해보기
                • [코드스니펫] 스택 네비게이터 설치 코드
                • yarn add @react-navigation/stack
                • [실습 ✍️] 적용하기</aside>
                  • [코드스니펫] StackNavigator.js 스택 네비게이터
                  • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
                  <aside> 👉 워워 진정하세요! 위에서부터 차근차근 살펴볼거에요 🙂 언제나 우린 미니카 조립 설명서를 보면서 조립하듯, 공식 문서 내용 그대로 적용한다는 것 꼭 잊지마시고! 살펴보도록 하겠습니다.
                  • 💻스택 네비게이터 코드 분석
                    • 공식 문서 링크
                    • 적용 순서 1) 사용 준비
                    • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator();
                    • 적용 순서 2) 기본 틀
                    • //리액트의 모~든 파일은 컴포넌트라 생각하고 //페이지 기능을 해주는 모든 기능이 담겨 있는 컴포넌트를 만든다 생각하세요! const StackNavigator = () =>{ return ( /// 페이지 기능이 들어갈 곳 ) } export default StackNavigator;
                    • 적용 순서 3) 스크린 옵션
                    • //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
                    • 적용 순서 4) 페이지 연결
                    • {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/>
                  <aside> 👉 컴포넌트를 페이지화 했고, 페이지를 이동할 수 있는 네이게이션도 준비가 됐다면, 우리는 최상단 컴포넌트 즉 App.js에 네비게이션 기능을 달아야 합니다.</aside>
                  • [코드스니펫] App.js
                  • import React from 'react'; //이제 모든 페이지 컴포넌트들이 끼워져있는 책갈피를 메인에 둘예정이므로 //컴포넌트를 더이상 불러오지 않아도 됩니다. // import MainPage from './pages/MainPage'; // import DetailPage from './pages/DetailPage'; import { StatusBar } from 'expo-status-bar'; //메인에 세팅할 네비게이션 도구들을 가져옵니다. import {NavigationContainer} from '@react-navigation/native'; import StackNavigator from './navigation/StackNavigator' export default function App() { console.disableYellowBox = true; return ( <NavigationContainer> <StatusBar style="black" /> <StackNavigator/> </NavigationContainer>); }
                  • 스택 네비게이터 적용 후 MainPage 모습
                • 즉, 앱 가장 최상위 코드에 네비게이션을 다는겁니다 그래야 앱 어디서든 원하는 페이지 이동이 가능할테니까요!
                • </aside>
                • <aside> 👉 navigation 폴더 하나를 만들고 StackNavigator.js 파일을 만들어주세요 그리고 StackNavigator 안에 다음 코드를 넣어주세요
              • 페이지 헤더 수정</aside><aside> 💡 그 이유는 StackNavigator에서 현재 헤더에 대한 스타일과 페이지에 헤더의 제목까지 결정해주고 있기 때문입니다.
                • 스택 네비게이터의 헤더 스타일 부분 코드
                • <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTintColor: "#fff", headerBackTitleVisible: false }} > {/* name에 해당 하는 부분이 페이지의 타이틀이 됩니다.*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
                <aside> 💡 따라서 헤더 스타일을 적절히 바꾸기 위해 다음과 같이 옵션 부분의 코드를 수정해보도록 하겠습니다.
                • [코드스니펫] StackNavigator.js
                • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
                • [코드스니펫] MainPage.js 에서 title 삭제!
                • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                • StackNavigator 옵션 코드 수정 후 모습
                <aside> 💡 그래도 위에 페이지 제목으로 MainPage가 되는 모습은 뭔가 또 수정이 필요해보입니다. 이를 위해선 다음 스택 네비게이션 페이지 이동 강의에서 살펴보도록 하겠습니다
              • </aside>
              • </aside>
              • </aside>
              • <aside> 💡 그런데 현재 MainPage 화면은 다음과 같이 상단의 모습이 약간 이상해 보입니다.
              • 페이지 이동하기일단 페이지를 이동시키려면, 책갈피가 페이지들에게 부여해준 페이지 이동 기능을 사용해야 합니다.</aside><aside> 💡 일단 먼저 구조를 보여드렸습니다. 물론 사용을 해봐야 감이 오겠죠?같이 해보죠!
                • [실습 ✍️] 데이터 없이 페이지 이동하기
                  • [코드스니펫] MainPage.js
                  • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (**<Card content={content} key={i} navigation={navigation}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                  • [코드스니펫] Card.js
                  • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 **export default function Card({content,navigation}){** return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage')}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                • navigation.navigate("DetailPage")
                • [실습 ✍️] 데이터 가지고 페이지 이동하기</aside><aside> 💡 그럼 Card에서 DetailPage로 이동할 때, MainPage로 부터 넘겨받은 content도 넘겨볼까요?</aside>
                  • [코드스니펫] Card.js
                  • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function Card({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage',**content**)}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                  • [코드스니펫] DetailPage.js
                  • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage({navigation,route}) { //초기 컴포넌트의 상태값을 설정 //state, setState 뿐 아니라 이름을 마음대로 지정할 수 있음! const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) //Card.js에서 navigation.navigate 함수를 쓸때 두번째 인자로 content를 넘겨줬죠? //content는 딕셔너리 그 자체였으므로 route.params에 고대~로 남겨옵니다. //즉, route.params 는 content죠! navigation.setOptions({ //setOptions로 페이지 타이틀도 지정 가능하고 title:route.params.title, //StackNavigator에서 작성했던 옵션을 다시 수정할 수도 있습니다. headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                  • 이동한 디테일 페이지 모습
                  <aside> 💡 건네 받은 값을 꺼낼 땐, 다음과 같이 책갈피가 navigation과 추가적으로 건네준 route에서 꺼내 확인 할 수 있습니다.</aside>
                  • route에 담겨져 오는 데이터 콘솔에서 직접 확인
                  • DetailPage에서 상태값을 초기에 설정한 이유</aside><aside> 💡 그 이유는 컴포넌트가 화면에 그려지는 순서에 있습니다. 이건 지금 단계에선 조금 과한감이 있어서 간략히만 설명해보면 다음과 같습니다.
                    1. DetailPage 컴포넌트가 useState에 들어 있는 
                       데이터 가지고 화면에 그려짐(return 함수실행)
                    2. 화면에 다 그려진후, useEffect 함수 실행
                    3. useEffect에서 상태값 변경 이벤트가 실행되면 변경된 데이터 가지고 다시 return 실행
                    4. 변경된 데이터를 가지고 화면에 DetailPage가 다시 그려짐.
                    
                    <aside> 💡 곰곰히 생각해보면 결국, 리액트 네이티브에서 화면이 변경되는 시점은 컴포넌트의 상태값이 변경될 때니까요!또는! 우리가 한번 배웠떤 Loading.js를 이용하여 데이터가 준비가 되면 로딩 화면을 치우고, 본 화면을 보여준다던가!</aside>
                  • 여러분들 마음껏 화면 처리를 해주시면 됩니다!
                  • 이 개념은 어려울 수 있습니다 충분히! 따라서 처음에는, 어떠한 데이터를 보여주는 컴포넌트라면, 무조건 초기값을 의미없는 값이더라도 넣고 시작한다! 라고 생각하시면 편합니다!
                  • </aside>
                  • //초기 컴포넌트의 상태값을 설정 const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" })
                  • <aside> 💡 DetailPage 초반에 우린 이렇게 상태값을 설정해놨었습니다. 그 이유가 뭘까요? 심지어 이 상태값을 초기에 설정안하면 오류가 발생합니다. tip엔 아무것도 없다며...
                • route.params 객체에 건네준 딕셔너리가 넘겨 있습니다!
                • 왜냐하면 지금은 어떠한 카드를 눌러도 상세 페이지에서 동일한 데이터를 보게 되잖아요? tip 데이터를 고정해놨으니까요!
                • navigation.navigate("Detail",**{** title**:** title **})**
                • <aside> 💡 버튼 카드에서 사용한 함수에 아래와 같이 두 번째 인자로 딕셔너리를 넘겨주면, 우리는 이동 한 페이지에서 넘겨준 데이터를 받을 수 있습니다.
              • </aside>
              • 그럼 이제 Card.js에 페이지 이동 기능을 달아보겠습니다. 코드를 실행 한 다음 페이지를 이동해 보면 익숙한 뒤로 가기 버튼도 보일거에요!
              • //navigation 객체가 가지고 있는 두 함수(setOptions와 navigate) //해당 페이지의 제목을 설정할 수 있음 navigation.setOptions({ title:'나만의 꿀팁' }) //Stack.screen에서 name 속성으로 정해준 이름을 지정해주면 해당 페이지로 이동하는 함수 navigation.navigate("DetailPage") //name 속성을 전달해주고, 두 번째 인자로 딕셔너리 데이터를 전달해주면, Detail 페이지에서 //두번째 인자로 전달된 딕셔너리 데이터를 route 딕셔너리로 로 받을 수 있음 navigation.navigate("DetailPage",**{ title: title }**) //전달받은 데이터를 받는 route 딕셔너리 //비구조 할당 방식으로 route에 params 객체 키로 연결되어 전달되는 데이터를 꺼내 사용 //navigate 함수로 전달되는 딕셔너리 데이터는 다음과 같은 모습이기 때문입니다. /* { route : { params :{ **title:title** } } } */ **const {** title**} = route.params;**
              • Stack.screen에 등록된 모든 페이지 컴포넌트들은 navigation 와 route 라는 딕셔너리(객체)를 속성으로 넘겨받아 사용할 수 있습니다. 이 두 딕셔너리는 다음과 같은 기능을 갖습니다.
              • <aside> 💡 Main 페이지(MainPage.js)에서 카드 버튼을 누르면(Card.js) 꿀팁 상세 페이지(DetailPage.js)로 이동합니다
              06**. [Expo 앱다운 앱기능] 공유 기능과 외부 링크 추가**
              • Share그런데 결과를 친구한테 공유하고 싶은 마음이 들수도 있겠네요 결과를 공유해주고, 앱다운로드 주소까지 전달해준다면 여러분이 만든 앱 홍보하기가 수월하겠죠?
              • </aside>
              • <aside> 👉 디테일 페이지까지 이동해서 도달했다면 여러분은 앱 개발자!
              • Share 적용해보기
                • 준비!
                  • 설치 할 라이브러리 없이, react-native에서 기본적으로 제공해주는 공유 기능을 사용합니다.
                  import { Share } from "react-native";
                  
                  • 그리고 우린 다음과 같은 공유 버튼을 디테일 페이지 하단에 생성하고 기능을 만들꺼에요! 한번 잠깐 영상을 멈추고 기능은 생각 말고 다음 버튼을 추가한 화면만 그려보세요! 복습 복습
                  • 버튼 추가 디테일 페이지 모습
                • [실습 ✍️] 적용</aside>
                  • [코드스니펫] DetailPage.js
                  • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { **Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); }** return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}**>팁 공유하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:100, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                  • 공유하는 모습
                • <aside> 💡 share 함수를 만들고 거기에 react-native가 기본적으로 제공해주는 Share 함수를 사용하여 간단히 공유를 할 수 있습니다
                • 공유 모습
              • Linking이번엔 앱에서 외부 링크를 여는 방법에 대해 배워봅니다. 버튼을 누르면, 버튼에 연결 시킨 기능을 통해 외부 링크를 핸드폰에 있는 기본 브라우저로 열어봅니다.
              • </aside>
              • <aside> 👉 디테일 페이지에 있는 내용들이 사실 어디에서 가져온거라면? 출처를 남겨야 할겁니다. 그리고 그 출처로 바로가기 버튼정도도 있으면 여러분들이 만드는 다양한 플랫폼들을 서로 연결 시킬 수 있겠죠?
              • Linking 적용해보기
                • 준비!
                  • DetailPage에 버튼을 다음과 같이 추가해주세요
                  • 링크 버튼 추가 된 DetailPage 모습
                  • expo 에서 제공해주는 도구를 설치 한다음, 해당 도구를 상단에 가져와 준비해야 합니다
                  expo install expo-linking
                  
                  import * as Linking from 'expo-linking';
                  
                • [실습 ✍️] 적용
                  • [코드스니펫] DetailPage.js
                  • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; import * as Linking from 'expo-linking'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); } const link = () => { Linking.openURL("<https://spartacodingclub.kr>") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}>팁 공유하기 link()}>외부 링크 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:90, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
              07**. 3주차 끝 & 숙제 설명**
                1. 어바웃 화면 페이지화 시키고 버튼 추가하기!
                <aside> 👉 지난번 숙제로 만든 AboutPage.js를 아직 우린 스택 네비게이터에 연결하지 않았습니다.추가적으로 외부 링크 걸기까지 배웠으니, 여러분 인스타나 블로그 주소 혹은 아무주소를 AboutPage 하단 버튼에 연결해주세요!
                • 만들 화면
                • 가이드
                  1. 메인에 TouchableOpacity 버튼을 두고 navigate를 연결시키면 이동이 가능하겠죠?
                  2. Stack.screen에 AboutPage.js를 연결시켜야합니다!
                  3. 소개 페이지 상태바의 디테일함을 주목하세요! 흰색이네요 🙂
              • </aside>
              • 따라서 여러분이 직접! 스택네비게이터에 AboutPage.js를 달아보구요! 하단 메인페이지의 상단에 다음과 같은 버튼을 만들어서 누르면 AboutPage로 가게끔 해주세요!
                1. 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요
              • 가이드
                  1. 찜 데이터 제공
                  <aside> 💡 나만의 꿀 팁 앱에서 찜 페이지란 쉽게 말해 팁 리스트 중에 선택한 팁들을 모아보는 페이지입니다. 이런 찜 페이지 자주 보셨죠?
                  • 즉, 다음 상태값으로 화면을 구성해주세요::: 리스트에 딕셔너리 두 개가 들어있는 데이터입니다.
                  </aside>
                  • [코드스니펫] 찜 상태 데이터
                  • const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }])
                • 하지만 아직 데이터를 저장하고, 수정하고 등등의 데이터 관리 부분을 배우지 않았기 때문에 [코드스니펫] 찜 상태 데이터를 가지고 화면만 그려볼겁니다.
                  1. 파일의 위치와 이름
                  <aside> 💡 새로운 페이지를 만드는 중이니, 어디에 위치해야 할까요? 당연히 pages 폴더에 위치시켜야 겠죠?</aside>
                • 또한 파일이름은 우리 LikePage.js 로 만들어 봅시다! ZzimPage는 좀...
                  1. 메인의 꿀팁찜 버튼을 누르면 찜 페이지로!
                  <aside> 💡 혼자만의 큰그림이 있었습니다. 메인페이지에 꿀팁찜 버튼이란걸 강의 초반부터 만들어 놓았었어요! 이 버튼을 누르면 찜 페이지로 가게끔 해봅시다!
                • </aside>
                  1. Card.js 를 복붙해서 찜 페이지에서만 사용하는 LikeCard.js 만들기
                  <aside> 💡 찜 목록도 Card.js와 크게 다를건 없습니다. 기존 카드에 찜 삭제 버튼정도가 추가 되었다는 점?이 다르므로, Card.js 코드를 복붙해서 LikeCard.js를 만들어 봅시다</aside>
                • 그런데! LikeCard.js에선 TouchableOpacity로 감싸는게 아닌! View 태그로 카드 전체를 감싸주도록 바꿔주세요!
                  1. 최종 폴더 & 파일 모습
              HW. 3주차 숙제 해설
              • 숙제 1: 어바웃 화면 페이지화 시키고 버튼 추가하기!
                • [코드스니펫] StackNavigator.js(숙제1)
                • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> </Stack.Navigator> ) } export default StackNavigator;
                • [코드스니펫] AboutPage.js(숙제1)
                • import React,{useEffect} from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' import { StatusBar } from 'expo-status-bar'; export default function AboutPage({navigation,route}){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" useEffect(()=>{ navigation.setOptions({ title:"소개 페이지", headerStyle: { backgroundColor: '#1F266A', shadowColor: "#1F266A", }, headerTintColor: "#fff", }) },[]) return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:20, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
                • [코드스니펫] MainPage.js(숙제1)
                • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20, }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
              • 숙제 2: 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요!
                • [코드스니펫] StackNavigator.js(숙제2)
                • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; import LikePage from '../pages/LikePage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> <Stack.Screen name="LikePage" component={LikePage}/> </Stack.Navigator> ) } export default StackNavigator;
                • [코드스니펫] LikeCard.js(숙제2)
                • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function LikeCard({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <View style={styles.card}> <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> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                • [코드스니펫] MainPage.js(숙제2)
                • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> **<TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>** </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
                • [코드스니펫] LikePage.js(숙제2)
                • import React,{useState, useEffect} from 'react'; import {ScrollView, Text, StyleSheet} from 'react-native'; import LikeCard from '../components/LikeCard'; import Card from '../components/Card'; export default function LikePage({navigation,route}){ const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }]) useEffect(()=>{ navigation.setOptions({ title:'꿀팁 찜' }) }) return ( { tip.map((content,i)=>{ return() }) } ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#fff" } })01. 오늘 배울 것
                  • 오늘 배울 내용이 두 가지는 앞으로도 계속~ 반복 숙달합니다 🙂
                    1. 리액트 필수 지식 활용
                    2. Expo 기능 사용
                    3. 페이지 적용
                    </aside>
                  • 오늘은 앱다운 앱을 만드는 연습과 기술을 배울텐데요! 다음 순서대로 지금까지 배웠던 것을 복습 하면서 앱다운 앱 기능을 배우니 점점 더 여러분은 앱 개발에 숙달되는 모습을 볼 수 있습니다!
                  • <aside> 👉 지난 시간까지 자바스크립트 기초체력을 다지고 JSX로 화면을 그리는 방법 까지 배워봤습니다.
                  • [앱 필수 기초지식] 리액트 필수지식
                    • 컴포넌트(Component)
                    • 상태(State,useState)
                    • 속성(Props)
                    • useEffect
                    이렇게 네 가지입니다. 단어가 조금 어려워 보이지만, 재미있고 간단한 개념들입니다. 이 개념들을 통해 앱 코드를 체계적으로 관리할 수 있고 화면안에서 데이터를 자유자재로 관리할 수도 있습니다.리액트 네이티브(React Native)는 리액트(React.js) 기반으로 만들어진 앱 개발 기술이기 때문이에요! 겁먹지마시고 차근차근 다루어봅시다.
                  • </aside>
                  • 최소한의 리액트 개념을 배우는 이유는
                  • <aside> 👉 리액트 네이티브 앱을 만들기 위해서 알아야 하는 최소한의 리액트 개념을 배웁니다.
                  • [Expo 앱다운 앱기능] 앱다운 앱을 위해 사용할 리액트 네이티브와 Expo 기능들이렇게 앱 다운 앱이 되기위한 기능들을 공식문서를 보고 하나하나 적용해보는 시간을 갖습니다. 이 연습을 거치면 여러분들은 공식문서를 보며 필요한 앱 기능들을 여러분들 앱에 붙여나갈 수 있습니다!
                  • </aside>
                  • <aside> 👉 앱이라고 하면 보통 현재 보고 있는 내용을 친구들한테 공유하거나, 앱 안에 있는 링크를 누르면 바로 해당 링크로 화면이 전환되는 기능들이 들어 있습니다. 또한 배터리를 볼 수 있는 상태바 스타일도 앱 전체적인 스타일에 맞게 변경할 수도 있어야 할겁니다.
                  • [앱 페이지 적용] 앱에 페이지 기능 넣기!그래서 앱안에 여러 페이지를 두고 버튼을 눌러 이동할 수 있는 기능을 달아봅니다. 본격적으로 거의 온전한 앱의 모습을 띄게 됩니다!
                  • </aside>
                  • <aside> 👉 앱에 페이지가 하나라면 너무 단조롭고 볼게 없는 앱이 되겠죠?
                  02. [함께해보기] 나만의 꿀팁 앱 상세 화면
                  • [실습 ✍️ ] 나만의 꿀팁 상세 페이지가될 아래의 화면을 만들어보세요
                  • 퀴즈 가이드
                    • [현재 코드 상황]
                      • App.js
                      • import React from 'react' import MainPage from './pages/MainPage'; import AboutPage from './pages/AboutPage'; export default function App(){ // return (<MainPage/>) return (<AboutPage/>) }
                      • MainPage.js
                      • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { 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> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, 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", } });
                      • AboutPage.js
                      • import React from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' export default function AboutPage(){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:100, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
                      • 폴더구조
                    • DetailPage.js를 pages 폴더에 만들어 넣기</aside>
                    • <aside> 👉 저번 1주차 마무리 시간에, pages 폴더를 만들고 AboutPage.js 파일을 만들어 넣었던 기억이 나시나요? 그때랑 동일하게 pages 폴더안에 DetailPage.js 파일을 만들고 App.js에서 return 부분에 DetailPage.js를 넣어주세요 다음과 같이요!
                    • 문제 디테일 화면을 위한 데이터딕셔너리 형태로 키값에 접근해서 값을 꺼내 사용해도 되고! 문자열들 그대로 복사해서 Text 태그에 넣어 사용해도 됩니다!
                      • [코드스니펫] 상세 화면에서 사용될 데이터
                      • const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }
                    • </aside>
                    • <aside> 👉 화면에 나타낼 꿀 팁은 다음 데이터를 사용하세요.
                  • 함께 해보기</aside>
                    • [코드스니펫] DetailPage.js 화면
                    • import React from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage() { const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" } const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                  • <aside> 👉 DetailPage를 확인하려면 App.js를 좀 바꿔야 겠죠? 어떻게 바꿔야 할까요?
                  03**.** [앱 필수 기초지식] 컴포넌트, 속성, 상태, useEffect
                  • 공부 할 리액트 필수 지식<aside> 👉 다시 떠올리자면, 리액트 네이티브는 리액트(React.js) 라이브러리 기반으로 만들어진 프레임워크 입니다. 그렇기 때문에 기본적인 구조는 리액트를 닯아 있습니다.1) 컴포넌트(Component) : 정해진 엘리먼트들(요소)을 사용하여 만든 화면의 일부분 2) 상태(State) : 컴포넌트에서 데이터를 유지하고 관리하기 위한 유일한 방법 == 그냥 사용할 데이터! 3) 속성(Props) : 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하는 방식 == 그냥 데이터 전달! 4) useEffect : 화면에 컴포넌트가 그려지면 처음 실행해야 하는 함수들을 모아두는 곳</aside>
                  • 네 가지가 있습니다. 워워 어렵지 않아요. 이것도 정해진 규칙과 개념일 뿐! 사용방법 숙지!만 하면 그만이랍니다!
                  • 대표적으로
                  • 컴포넌트(Component)</aside><aside> 👉 컴포넌트는 즉, 화면의 모든 부분입니다. 그리고 컴포넌트란 App.js의 큰 App 함수처럼, 코드 전체를 감싸고 있는 함수를 뜻하기도 합니다.</aside>이렇게 컴포넌트로 생각하고 앱을 개발해 나아간다면, 코드 재사용이 용이 해집니다.실제 우리 예제에 어떻게 적용할 수 있는지. 확인해보겠습니다.
                  • </aside>
                  • 코드 재사용이란 어려운 용어는 아니고, 버튼 하나를 만들었을 때 이 버튼 코드를 여러 페이지에서 사용 가능하다는 뜻입니다.
                  • <aside> 👉 버튼 하나가 컴포넌트가 될 수 있고, 버튼을 모아둔 영역이 컴포넌트가 될 수 있습니다.
                  • 따라서 App.js를 App 컴포넌트라고도 부를 수 있습니다.
                  • <aside> 👉 컴포넌트는 UI의 요소, 요소를 재사용 가능한 부분으로 조각내서 운영하는 기법입니다. 리액트 기반으로 만들어진 페이스북 웹사이트는 운영되는 컴포넌트가 수 만가지라고 합니다
                  • [실습 ✍️ ] 메인화면 컴포넌트화 해보기그럼 다음 코드를 Card.js에 넣어주세요! 어디서 많이 본 코드입니다 그쵸?<aside> 🚧 마찬가지로 MainPage.js 화면을 확인하려면 App.js를 바꿔야 겠죠? return (<MainPage/>) 으로요!
                    • [코드스니펫] Card.js
                    • import React from "react" import {View,Text,Image,StyleSheet} from "react-native"; //비구조 할당 방식으로 넘긴 속성 데이터를 꺼내 사용함 export default function Card({content}) { return (<View style={styles.card}> <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>) } const styles = StyleSheet.create({ 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", } })
                    <aside> 👉 맞습니다! MainPage.js에서 이미지위에 텍스트가 있던, 한 줄에 세 개씩 나열 되었던 그 카드 버튼입니다!.</aside>
                    • [코드스니펫] MainPage.js
                    • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; **import Card from '../components/Card';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (**<Card content={content} key={i}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                    <aside> 👉 MainPage.js에 있던 코드들을 Card.js로 옮겼습니다. 이렇게 옮기게 되면, 추후에 MainPage.js가 아닌 다른 페이지(페이지 또한 컴포넌트로 볼 수 있습니다) 또는 컴포넌트에서 Card.js를 사용할 수 있습니다.</aside>
                    • MainPage.js
                    • Card.js
                    <aside> 👉 미리 말씀드리자면 MainPage.js에서 Card.js로 데이터를 넘기고 있는 모습입니다! 이를 속성을 넘긴다! 라고 보통 일컫는데 이는 다음 챕터에서 더 자세히 다루도록 하겠습니다!
                  • </aside>
                  • 그런데 약간 생소한 부분이 있습니다 다음 두 부분인데요!
                  • 그럼 MainPage.js는 다음과 같이 변경해주세요
                  • </aside>
                  • </aside>
                  • <aside> 👉 Components 폴더 안에 Card.js라는 파일을 만듭니다. 보통 컴포넌트들은 components 폴더에 모아두면서 관리하곤 합니다.
                  • 속성(Props)<Text> 태그엔 numberOfLines이란 속성이 있었습니다. 말줌임표 효과를 주었죠! <Image> 태그엔 resizeMode란 속성이 있었습니다. 이미지가 영역을 차지하는 방식을 나타냈죠.</aside>예를 들어 Card를 사용하는 곳에서<Card image={'이미지 주소'}/> 라고 속성을 써 넣으면, 이 <Card/>는 image라는 속성이름에 {'이미지 주소'} 값을 을 갖게 됩니다. 즉 키와 벨류의 값을 갖게 됩니다 근데 이게 무슨 의미가 있을까요?</aside>
                  • 의미가 있습니다. 속성을 부여받은 컴포넌트에서 해당 속성 값을 받아서 사용 할 수 있거든요! 다음과 같이 말이죠!
                  • <aside> 👉 우리가 만든 <Card/> 컴포넌트에도 속성이 존재합니다. 우리가 부여 하면 부여하는 대로 속성이 되는 겁니다.
                  • 이 모든 속성들은 공식 문서에 나와 있고, 해당 엘리먼트들이 태어날 때부터 가지고 있는 속성이였습니다.
                  • <aside> 👉 속성은 쉽게 생각해서 컴포넌트에 데이터를 전달한다는 것이다. 그 전달 모습은 키와 벨류의 형태입니다.
                  • [확인해보기👀 ]</aside>
                    • MainPage.js 에서의 Card.js 컴포넌트
                    <aside> 👉 어렵지 않은 규칙이 숨어 있습니다!
                    1. 컴포넌트에 속성(데이터)을 부여해줘서 전달할땐, 키와 벨류(content={content}) 형태로 전달해줘야 할 것
                    2. 컴포넌트를 반복문 돌릴땐, 컴포넌트마다 고유하다는 것을 표현하기 위해, map에서 나오는 인덱스(i)를 key = {i} 속성 전달 형태로 꼭 넣을것! 입니다.
                    </aside>
                    • Card.js 에서의 속성 값 내려 받기
                    <aside> 👉 오랜만에 복습겸 나온 비구조 할당 방식 의 모습입니다. MainPage.js에서 넘겨준 속성은 실제 받게되는 컴포넌트에서 정말 딕셔너리 데이터를 받았다! 라고 생각하면 됩니다.이 방식으로 넘겨준 키값을 {키,키,키} 비구조 할당 방식으로 바로 꺼내서 사용하면 됩니다!
                  • </aside>
                  • 때문에, 비구조 할당 방식이 뭐였죠? 딕셔너리에서 키값을 바로 취해서 변수로써 함수안에서 즉시 사용할 수 있는 방식이였죠?
                  • <aside> 👉 MainPage.js에서 방금전 컴포넌트화! 했던 Card.js를 우린 이렇게 사용했습니다.
                  • 상태(State, useState)<aside> 👉 컴포넌트마다 데이터를 보유하고 관리 할 수 있습니다. 데이터라고 불러도 되지만, 리액트에서는 컴포넌트에서 보유/관리 되는 데이터를 **상태**라 부릅니다.사용방법은 실습을 통해 알아보도록 하겠습니다!
                  • </aside>
                  • 리액트에서 상태(state)는 리액트 라이브러리에서 제공해주는 useState로 생성하고 setState 함수로 정/변경 할 수 있습니다.
                  • useState, 그렇다면 왜 중요할까?여러분이 만드는 화면은 데이터에 따라 변경됩니다 또 아무 데이터가 아니라 이 상태(state)로 관리되는 데이터가 변경되면 화면이 바뀝니다.</aside><aside> 👉 당시 개발자들끼리 이 상태와 컴포넌트의 관계를 공식으로 표현한 글을 보고 대단하다!! 했던 기억이 있는데, 여러분들은 어떻게 느끼실지 궁금하네요 ㅎㅎ무슨말인지 아직 잘 와닿지 않으시죠? 다른건 잘 와닿지 않아도 state는 컴포넌트에서 관리되는 상태만 기억하시면 충분합니다 이 개념은 이번 강의에서 개념을 다 배운 뒤, 다음 6 강에서 실습 하며 직접 확인할 수 있습니다
                  • </aside>
                  • 저 공식은 즉, 사용자 화면(UI)는 컴포넌트(component)에 어떤 데이터(state)가 주입되고 변경되냐에 따라 변화된다를 뜻합니다
                  • UI = component(state)
                  • 모르셔도 되지만 꽤 유의미한 공식이 있습니다
                  • <aside> 👉 리액트는 특이한 점이 있습니다.
                  • 화면이 그려진다음 가장 먼저 실행되는 함수, useEffect화면이 그려진 다음 가장 먼저 실행되는 함수로 사용방법이 간단합니다 형식만 알고 있으면 충분합니다
                    **useEffect(()=>{**
                    
                    	...화면이 그려진 다음 가장 먼저 실행되야 할 코드 작성 공간
                    
                    **},[])**
                    
                    <aside> 👉 useEffect(()=>{ },[])</aside>
                  • 이 안에서, 화면이 그려진다음 실행시키고 싶은 함수를 작성한다면 가장먼저 실행이 됩니다. useEffect 또한 어떤 역할을 하는지만 알고! 바로 실습을 통해 확인해보도록 하겠습니다
                  • </aside>
                  • <aside> 👉 useEffect는 너무 간단하고 유용하게 쓰이는 리액트 기본 제공 함수입니다.
                  • useEffect, 그렇다면 왜 중요할까?
                    1. 화면이 그려진다
                    2. useEffect가 데이터를 준비한다
                    3. 상태 데이터가 업데이트 되었으니 화면이 다시 그려진다
                    화면이 그려진 다음, 서버에게 필요한 데이터를 요청하여 받은 후, 화면을 다시그릴때 주로 사용되는데요! 아주 일반적인 패턴이므로 여러분들도 금방 이해하고 익숙해지게 됩니다!
                  • </aside>
                  • <aside> 👉 보통 useEffect는 데이터를 준비할 때 사용합니다 데이터를 준비한다는 것은, 데이터를 서버로부터 혹은 어디선가로부터 받은 후 상태(state)에 반영한다는 것을 뜻합니다. 이런순서로 말이죠
                  • [실습✍️ ] data.json을 상태(state)에 넣어서 관리해보기복습하실때 주석도 자세히 살펴보세요!
                    • [코드스니펫] MainPage.js
                    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //useState 사용법 //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수 //setState는 state를 변경시킬때 사용해야하는 함수 //모두 다 useState가 선물해줌 //useState()안에 전달되는 값은 state 초기값 const [state,setState] = useState([]) //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수 //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음 useEffect(()=>{ setState(data) },[]) // let tip = data.tip; //data.json 데이터는 state에 담기므로 상태에서 꺼내옴 let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                    <aside> 👉 그러나 실행을 하면 다음과 같은 오류 화면이 나옵니다
                    • 오류화면
                    <aside> 👉 그 이유는 이런 절차로 발생됩니다.
                    1. 화면이 그려진다
                    2. useEffect가 데이터를 state에 useState를 이용하여 업데이트한다
                    3. 상태(state)가 변경되었으니 화면이 다시 그려진다
                    여기서 1번에서 오류가 난겁니다. 화면이 가장 먼저 그려질때, 우리가 Card에 데이터를 넘기기위해 어떤일을 했나요?이런 오류는 우리가 지금 배운 개념들, 상태, 컴포넌트, useEffect를 이용하여 로딩화면을 만들어줌으로써 해결이 가능합니다!
                  • </aside>
                  • 처음부터 데이터가 없어서 , 꺼내올수 없는 오류
                  • <aside> 👉 상태(state)에서 tip 키 값에 접근하여 데이터를 꺼내 map 즉, 반복문을 돌리려했습니다. 그런데 state에 데이터가 처음부터 있었나요? 없었죠? useEffect를 거쳐야 state에 데이터가 담기는데 아직 없는 상태라 오류가 나는 겁니다.
                  • </aside>
                  • </aside>
                  • </aside>
                  • <aside> 👉 지금 꿀팁을 data.json 파일에서 꺼내 바로 JSX 문법에서 사용하고 있습니다. 이를 다음과 같이 상태에 저장한 다음 사용하도록 변경해보도록 하겠습니다.
                  04. [앱 필수 기초지식 응용] 로딩화면, 카테고리 기능, 상태 바
                  • 앱 운영에 필요한 로딩화면이때 우린 로딩화면을 만들어줌으로써 쉽게 해결이 가능합니다. 컴포넌트 폴더에 Loading.js 파일을 만들어주고 다음 코드를 넣어주세요
                    • [코드스니펫] Loading.js
                    • import React from 'react'; import {View,Text,StyleSheet} from 'react-native'; export default function Loading(){ return(<View style={styles.container}><Text style={styles.title}>준비중입니다...</Text></View>) } const styles = StyleSheet.create({ container: { //앱의 배경 색 flex:1, justifyContent:'center', alignItems:'center', backgroundColor: '#fdc453', }, title: { fontSize:20, fontWeight:'700' } })
                    <aside> 👉 그리고 MainPage.js 다음 코드 스니펫을 넣어주세요
                    • [코드스니펫] MainPage.js
                    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 const [state,setState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. **const [ready,setReady] = useState(true)** useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 **setTimeout(()=>{ setState(data) setReady(false) },1000)** },[]) // let tip = data.tip; let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" **//처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐)** return **ready ? <Loading/>** : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                    • 적용 모습
                    <aside> 👉 ready 라는 새로운 상태값이 추가되었습니다. 즉, 상태값은 컴포넌트안에 여러개가 될 수 있습니다.즉, 이런 순서로 진행된거에요!
                    1. ready 값이 true이므로 return 구문에서 ? 물음표 바로 뒤의 Loading 컴포넌트가 화면에 그려짐
                    2. 화면이 그려지고 난다음, 1초 이따가 상태값들이 채워지고 변경됨
                    3. ready 상태 값이 false가 됨
                    4. 상태값이 변경되었으므로 화면이 다시 그려짐
                    5. ready 값이 false 이므로 return 구문에서 : 콜론 뒤의 MainPage 컴포넌트가 화면에 그려짐
                    차근차근 순서따라 반복해서 보면 별거아닙니다. 정해진 규칙에 따라 우린 코드를 적용했고 정해진 규칙에 따라 코드가 실행되고 있는 거에요!
                  • </aside>
                  • useEffect안에 setTimeout이라는 함수가 존재합니다. , 뒤에 있는 숫자 (1000 === 1초) 만큼, 지연됐다가 안에 있는 코드가 실행되는 지연 함수입니다.
                  • </aside>
                  • </aside>
                  • <aside> 👉 5장에서 오류가 났던 것 처럼 화면이 그려질때 준비된 데이터가 없다!? 이러한 상황은 아주 빈번합니다.
                  • 카테고리 버튼상태값이 바뀌면 화면이 다시 그려진다! 라는 개념을 염두하고 한번 만들어보겠습니다!
                  • </aside>
                  • <aside> 👉 우리에게 이런 버튼이 있었습니다. 카테고리 버튼인데요! 이 버튼은 장식이아니라, 실제 누르면 하단의 데이터가 카테고리에 맞게 다시 정렬되는 기능이 의도되어 있습니다.
                  • [실습✍️ ] 카테고리 기능 넣기그리곤 함수가 필요합니다. 우리가 만든, 생활 재테크 반려견 버튼에 연결할 함수인데요!? 이 함수는 카테고리에 따라 카테고리 상태데이터를 새롭게 구성해주는 기능을 합니다.
                    • [코드스니펫] MainPage.js
                    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 **const [state,setState] = useState([])** //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 **const [cateState,setCateState] = useState([])** //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 **let tip = data.tip; setState(tip) setCateState(tip) setReady(false)** },1000) },[]) **const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } }** let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> **<TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>** <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, **middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 },** 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" }, **middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" },** cardContainer: { marginTop:10, marginLeft:10 }, });
                    • 제태크를 눌렀을때의 모습
                  • </aside>
                  • <aside> 👉 카테고리 기능을 위해선 먼저 카테고리 상태가 필요합니다. 카테고리에 맞는 꿀팁들을 지속적으로 저장하고 관리할 상태 그릇이죠!
                  • Expo에서 제공해주는 앱다운 앱 기능들</aside>
                  • <aside> 💡 Expo SDK. 일명 Expo에서 제공해주는 앱 기능 도구들을 개발할 때 여기서 확인 할 수 있습니다. (링크) 필요한 기능들이 있는지 목록을 쭉 보고, 원하는 기능을 선택해서 적용하면 끝!
                  • 상태 바란?</aside><aside> 💡 배터리가 충분한지, 몇시인지 매일 보시죠? 이부분도 우리가 처리를 할 수 있습니다.
                  • </aside>
                  • <aside> 💡 앱이 앱에 따라 모바일 맨 위 상태 바가 변하는 앱이 있습니다. 상태바란 이런 겁니다. 여러분들 매일 같이 보는 부분이에요!
                  • StatusBar
                    • 본격적으로 라이브러리 설치 시작!</aside><aside> 👉 터미널 우측 버튼중에 분할이란 버튼이 있습니다.</aside>
                    • 여러분들 터미널 왼편은 서버를 켜고 끄고를 담당하고(expo start) 터미널 우측은 필요한 라이브러리들을 설치할 때 명령어를 치는 장소로 분할해서 사용하면 편리합니다!
                    • <aside> 👉 이제 본격적으로 라이브러리들을 설치합니다. vscode에서 터미널을 이렇게 변경해주세요
                    • [코드스니펫] Expo 상태 바 설치
                    • expo install expo-status-bar
                    • [실습 ✍️] 적용적용 모습을 살펴볼까요?
                      • [코드스니펫] MainPage에 StatusBar 설치
                      • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; **import { StatusBar } from 'expo-status-bar';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> **<StatusBar style="black" />** <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                      <aside> 💡 style이 light일 때와, black일 때가 다릅니다. 우린 앱 화면 배경색을 검은색으로 설정했기 떄문에, 상태바를 black으로 하면 보이지 않습니다.Status Bar 공식문서
                      • black 일때
                      • light 일때
                    • </aside>
                    • 상태 바 속성은 공식문서에 다양하게 존재하니, 살펴보면서 앱에 적합한 상태 바를 적용해보세요!
                    • </aside>
                    • <aside> 💡 컴포넌트마다 다르게 적용할 수도 있고, 앱 전체에 공통적으로 적용할 수 있습니다. StatusBar를 컴포넌트 각각 다르게 둘 수 있습니다 우린 MainPage.js에 달아볼거에요!
                  05**. [앱** 페이지 적용**] 네비게이터 사용하기**
                  • 네비게이션이란?<aside> 👉 앱에 페이지 개념을 입혀주고! 웹 사이트를 이용하듯, 앱에서 여러분들이 만든 컴포넌트들을 페이지화 시켜주고, 해당 페이지끼리 이동을 가능하게 해주는 라이브러리입니다가령 다음 화면과 같이, 앱 하단에 탭 버튼을 두고 해당 버튼을 누르면 연결되어 있는 페이지로 바로바로 이동할 수 있게 해주는 기능(열 앱에서 많이 보셨죠?)도 가지고 있습니다.</aside>
                  • 따라서 이번시간에 같이 공부한 다음 틈틈히 다른 기능들도 적용해보세요!
                  • 이번 강의에선 앱 내의 페이지 구성 및 이동까지 직접 적용해볼 텐데요!, 이 라이브러리는 다양한 기능들을 가지고 있습니다.
                  • <aside> 💡 결국 이 라이브러리도 Expo에서 지원하고 있는 도구로써, 앱을 만들기 위해 사용 중인 Expo와 궁합이 아주 잘 맞습니다.
                  • </aside>
                  • <aside> 💡 물론 이것 또한 쉽게 페이징을 도와주는 외부 라이브러리를 가져다가 사용합니다. react-navigation 공식문서 보러가기 —> (링크)
                  • </aside>
                  • 현재 갖추고 있는 페이지 구성 확인!</aside>
                  • <aside> 💡 페이지 구성은 보통 pages 폴더 안에 생성해둔 컴포넌트로 구성합니다. 우리는 이미, MainPage.js 메인 페이지 DetailPage.js 상세 화면 페이지 그리고 숙제로 만든 소개화면인 AboutPage.js 까지 총 3 페이지를 가지고 있습니다.
                  • 기본 설치 코드우리에게 필요한 것이라고 했지만, 거의 대부분의 앱에 적용이 되어 있는 것들만 가져왔습니다.아래 명령어를 차례차례 터미널에 넣어 실행해주세요 가장 마지막줄은 꽤 깁니다. 이렇게 여러 라이브러리들을 띄어쓰기로 연결해서 한번에 설치 할 수도 있습니다.
                    • [코드스니펫] 네비게이션 설치 코드
                    • yarn add @react-navigation/native
                    • [코드스니펫] 네비게이션 추가 설치코드
                    • expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
                  • </aside>
                  • 아래 명령어들은 네비게이션을 사용하기 위해 필요한 기본 라이브러리들이구요! 우리가 곧 배울 스택 네비게이션와 탭 네비게이션 기능은 추가적으로 라이브러리를 또 설치 해줘야 합니다 😂
                  • <aside> 💡 리액트 네이티브에서 페이지 네비게이션을 구현하기란 조금 까다롭습니다. 그래서 딱 우리에게 필요한 것만 가져와 설치 하고, 적용해 나갈 계획입니다.
                  • 스택 네비게이션이란?</aside><aside> 💡 컴포넌트를 페이지화 시키는 스택 네비게이션은 다음과 같습니다.이렇게 만든 여러 페이지들을 책갈피 기능을 하는 스택 네비게이터에 모조리 등록시켜서, 언제든지 이 페이지 이동이 가능하게끔 해줍니다</aside>
                  • 페이지는 Stack.Screen 이라 부르며 책갈피는 Stack.Navigator라 부릅니다
                  • 우리가 페이지처럼 만든 컴포넌트를, 정말 페이지처럼 사용할 수 있게끔 페이지로 컴포넌트를 감싸 페이지로 만들어줍니다.
                  • <aside> 💡 스택 네비게이션은 컴포넌트에 페이지 기능을 부여해주고 컴포넌트에서 컴포넌트로 이동, 즉 페이지 이동을 가능하게 해줍니다
                  • createStackNavigator 사용해보기
                    • [코드스니펫] 스택 네비게이터 설치 코드
                    • yarn add @react-navigation/stack
                    • [실습 ✍️] 적용하기</aside>
                      • [코드스니펫] StackNavigator.js 스택 네비게이터
                      • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
                      <aside> 👉 워워 진정하세요! 위에서부터 차근차근 살펴볼거에요 🙂 언제나 우린 미니카 조립 설명서를 보면서 조립하듯, 공식 문서 내용 그대로 적용한다는 것 꼭 잊지마시고! 살펴보도록 하겠습니다.
                      • 💻스택 네비게이터 코드 분석
                        • 공식 문서 링크
                        • 적용 순서 1) 사용 준비
                        • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator();
                        • 적용 순서 2) 기본 틀
                        • //리액트의 모~든 파일은 컴포넌트라 생각하고 //페이지 기능을 해주는 모든 기능이 담겨 있는 컴포넌트를 만든다 생각하세요! const StackNavigator = () =>{ return ( /// 페이지 기능이 들어갈 곳 ) } export default StackNavigator;
                        • 적용 순서 3) 스크린 옵션
                        • //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
                        • 적용 순서 4) 페이지 연결
                        • {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/>
                      <aside> 👉 컴포넌트를 페이지화 했고, 페이지를 이동할 수 있는 네이게이션도 준비가 됐다면, 우리는 최상단 컴포넌트 즉 App.js에 네비게이션 기능을 달아야 합니다.</aside>
                      • [코드스니펫] App.js
                      • import React from 'react'; //이제 모든 페이지 컴포넌트들이 끼워져있는 책갈피를 메인에 둘예정이므로 //컴포넌트를 더이상 불러오지 않아도 됩니다. // import MainPage from './pages/MainPage'; // import DetailPage from './pages/DetailPage'; import { StatusBar } from 'expo-status-bar'; //메인에 세팅할 네비게이션 도구들을 가져옵니다. import {NavigationContainer} from '@react-navigation/native'; import StackNavigator from './navigation/StackNavigator' export default function App() { console.disableYellowBox = true; return ( <NavigationContainer> <StatusBar style="black" /> <StackNavigator/> </NavigationContainer>); }
                      • 스택 네비게이터 적용 후 MainPage 모습
                    • 즉, 앱 가장 최상위 코드에 네비게이션을 다는겁니다 그래야 앱 어디서든 원하는 페이지 이동이 가능할테니까요!
                    • </aside>
                    • <aside> 👉 navigation 폴더 하나를 만들고 StackNavigator.js 파일을 만들어주세요 그리고 StackNavigator 안에 다음 코드를 넣어주세요
                  • 페이지 헤더 수정</aside><aside> 💡 그 이유는 StackNavigator에서 현재 헤더에 대한 스타일과 페이지에 헤더의 제목까지 결정해주고 있기 때문입니다.
                    • 스택 네비게이터의 헤더 스타일 부분 코드
                    • <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTintColor: "#fff", headerBackTitleVisible: false }} > {/* name에 해당 하는 부분이 페이지의 타이틀이 됩니다.*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
                    <aside> 💡 따라서 헤더 스타일을 적절히 바꾸기 위해 다음과 같이 옵션 부분의 코드를 수정해보도록 하겠습니다.
                    • [코드스니펫] StackNavigator.js
                    • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
                    • [코드스니펫] MainPage.js 에서 title 삭제!
                    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                    • StackNavigator 옵션 코드 수정 후 모습
                    <aside> 💡 그래도 위에 페이지 제목으로 MainPage가 되는 모습은 뭔가 또 수정이 필요해보입니다. 이를 위해선 다음 스택 네비게이션 페이지 이동 강의에서 살펴보도록 하겠습니다
                  • </aside>
                  • </aside>
                  • </aside>
                  • <aside> 💡 그런데 현재 MainPage 화면은 다음과 같이 상단의 모습이 약간 이상해 보입니다.
                  • 페이지 이동하기일단 페이지를 이동시키려면, 책갈피가 페이지들에게 부여해준 페이지 이동 기능을 사용해야 합니다.</aside><aside> 💡 일단 먼저 구조를 보여드렸습니다. 물론 사용을 해봐야 감이 오겠죠?같이 해보죠!
                    • [실습 ✍️] 데이터 없이 페이지 이동하기
                      • [코드스니펫] MainPage.js
                      • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (**<Card content={content} key={i} navigation={navigation}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                      • [코드스니펫] Card.js
                      • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 **export default function Card({content,navigation}){** return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage')}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                    • navigation.navigate("DetailPage")
                    • [실습 ✍️] 데이터 가지고 페이지 이동하기</aside><aside> 💡 그럼 Card에서 DetailPage로 이동할 때, MainPage로 부터 넘겨받은 content도 넘겨볼까요?</aside>
                      • [코드스니펫] Card.js
                      • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function Card({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage',**content**)}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                      • [코드스니펫] DetailPage.js
                      • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage({navigation,route}) { //초기 컴포넌트의 상태값을 설정 //state, setState 뿐 아니라 이름을 마음대로 지정할 수 있음! const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) //Card.js에서 navigation.navigate 함수를 쓸때 두번째 인자로 content를 넘겨줬죠? //content는 딕셔너리 그 자체였으므로 route.params에 고대~로 남겨옵니다. //즉, route.params 는 content죠! navigation.setOptions({ //setOptions로 페이지 타이틀도 지정 가능하고 title:route.params.title, //StackNavigator에서 작성했던 옵션을 다시 수정할 수도 있습니다. headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                      • 이동한 디테일 페이지 모습
                      <aside> 💡 건네 받은 값을 꺼낼 땐, 다음과 같이 책갈피가 navigation과 추가적으로 건네준 route에서 꺼내 확인 할 수 있습니다.</aside>
                      • route에 담겨져 오는 데이터 콘솔에서 직접 확인
                      • DetailPage에서 상태값을 초기에 설정한 이유</aside><aside> 💡 그 이유는 컴포넌트가 화면에 그려지는 순서에 있습니다. 이건 지금 단계에선 조금 과한감이 있어서 간략히만 설명해보면 다음과 같습니다.
                        1. DetailPage 컴포넌트가 useState에 들어 있는 
                           데이터 가지고 화면에 그려짐(return 함수실행)
                        2. 화면에 다 그려진후, useEffect 함수 실행
                        3. useEffect에서 상태값 변경 이벤트가 실행되면 변경된 데이터 가지고 다시 return 실행
                        4. 변경된 데이터를 가지고 화면에 DetailPage가 다시 그려짐.
                        
                        <aside> 💡 곰곰히 생각해보면 결국, 리액트 네이티브에서 화면이 변경되는 시점은 컴포넌트의 상태값이 변경될 때니까요!또는! 우리가 한번 배웠떤 Loading.js를 이용하여 데이터가 준비가 되면 로딩 화면을 치우고, 본 화면을 보여준다던가!</aside>
                      • 여러분들 마음껏 화면 처리를 해주시면 됩니다!
                      • 이 개념은 어려울 수 있습니다 충분히! 따라서 처음에는, 어떠한 데이터를 보여주는 컴포넌트라면, 무조건 초기값을 의미없는 값이더라도 넣고 시작한다! 라고 생각하시면 편합니다!
                      • </aside>
                      • //초기 컴포넌트의 상태값을 설정 const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" })
                      • <aside> 💡 DetailPage 초반에 우린 이렇게 상태값을 설정해놨었습니다. 그 이유가 뭘까요? 심지어 이 상태값을 초기에 설정안하면 오류가 발생합니다. tip엔 아무것도 없다며...
                    • route.params 객체에 건네준 딕셔너리가 넘겨 있습니다!
                    • 왜냐하면 지금은 어떠한 카드를 눌러도 상세 페이지에서 동일한 데이터를 보게 되잖아요? tip 데이터를 고정해놨으니까요!
                    • navigation.navigate("Detail",**{** title**:** title **})**
                    • <aside> 💡 버튼 카드에서 사용한 함수에 아래와 같이 두 번째 인자로 딕셔너리를 넘겨주면, 우리는 이동 한 페이지에서 넘겨준 데이터를 받을 수 있습니다.
                  • </aside>
                  • 그럼 이제 Card.js에 페이지 이동 기능을 달아보겠습니다. 코드를 실행 한 다음 페이지를 이동해 보면 익숙한 뒤로 가기 버튼도 보일거에요!
                  • //navigation 객체가 가지고 있는 두 함수(setOptions와 navigate) //해당 페이지의 제목을 설정할 수 있음 navigation.setOptions({ title:'나만의 꿀팁' }) //Stack.screen에서 name 속성으로 정해준 이름을 지정해주면 해당 페이지로 이동하는 함수 navigation.navigate("DetailPage") //name 속성을 전달해주고, 두 번째 인자로 딕셔너리 데이터를 전달해주면, Detail 페이지에서 //두번째 인자로 전달된 딕셔너리 데이터를 route 딕셔너리로 로 받을 수 있음 navigation.navigate("DetailPage",**{ title: title }**) //전달받은 데이터를 받는 route 딕셔너리 //비구조 할당 방식으로 route에 params 객체 키로 연결되어 전달되는 데이터를 꺼내 사용 //navigate 함수로 전달되는 딕셔너리 데이터는 다음과 같은 모습이기 때문입니다. /* { route : { params :{ **title:title** } } } */ **const {** title**} = route.params;**
                  • Stack.screen에 등록된 모든 페이지 컴포넌트들은 navigation 와 route 라는 딕셔너리(객체)를 속성으로 넘겨받아 사용할 수 있습니다. 이 두 딕셔너리는 다음과 같은 기능을 갖습니다.
                  • <aside> 💡 Main 페이지(MainPage.js)에서 카드 버튼을 누르면(Card.js) 꿀팁 상세 페이지(DetailPage.js)로 이동합니다
                  06**. [Expo 앱다운 앱기능] 공유 기능과 외부 링크 추가**
                  • Share그런데 결과를 친구한테 공유하고 싶은 마음이 들수도 있겠네요 결과를 공유해주고, 앱다운로드 주소까지 전달해준다면 여러분이 만든 앱 홍보하기가 수월하겠죠?
                  • </aside>
                  • <aside> 👉 디테일 페이지까지 이동해서 도달했다면 여러분은 앱 개발자!
                  • Share 적용해보기
                    • 준비!
                      • 설치 할 라이브러리 없이, react-native에서 기본적으로 제공해주는 공유 기능을 사용합니다.
                      import { Share } from "react-native";
                      
                      • 그리고 우린 다음과 같은 공유 버튼을 디테일 페이지 하단에 생성하고 기능을 만들꺼에요! 한번 잠깐 영상을 멈추고 기능은 생각 말고 다음 버튼을 추가한 화면만 그려보세요! 복습 복습
                      • 버튼 추가 디테일 페이지 모습
                    • [실습 ✍️] 적용</aside>
                      • [코드스니펫] DetailPage.js
                      • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { **Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); }** return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}**>팁 공유하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:100, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                      • 공유하는 모습
                    • <aside> 💡 share 함수를 만들고 거기에 react-native가 기본적으로 제공해주는 Share 함수를 사용하여 간단히 공유를 할 수 있습니다
                    • 공유 모습
                  • Linking이번엔 앱에서 외부 링크를 여는 방법에 대해 배워봅니다. 버튼을 누르면, 버튼에 연결 시킨 기능을 통해 외부 링크를 핸드폰에 있는 기본 브라우저로 열어봅니다.
                  • </aside>
                  • <aside> 👉 디테일 페이지에 있는 내용들이 사실 어디에서 가져온거라면? 출처를 남겨야 할겁니다. 그리고 그 출처로 바로가기 버튼정도도 있으면 여러분들이 만드는 다양한 플랫폼들을 서로 연결 시킬 수 있겠죠?
                  • Linking 적용해보기
                    • 준비!
                      • DetailPage에 버튼을 다음과 같이 추가해주세요
                      • 링크 버튼 추가 된 DetailPage 모습
                      • expo 에서 제공해주는 도구를 설치 한다음, 해당 도구를 상단에 가져와 준비해야 합니다
                      expo install expo-linking
                      
                      import * as Linking from 'expo-linking';
                      
                    • [실습 ✍️] 적용
                      • [코드스니펫] DetailPage.js
                      • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; import * as Linking from 'expo-linking'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); } const link = () => { Linking.openURL("<https://spartacodingclub.kr>") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}>팁 공유하기 link()}>외부 링크 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:90, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                  07**. 3주차 끝 & 숙제 설명**
                    1. 어바웃 화면 페이지화 시키고 버튼 추가하기!
                    <aside> 👉 지난번 숙제로 만든 AboutPage.js를 아직 우린 스택 네비게이터에 연결하지 않았습니다.추가적으로 외부 링크 걸기까지 배웠으니, 여러분 인스타나 블로그 주소 혹은 아무주소를 AboutPage 하단 버튼에 연결해주세요!
                    • 만들 화면
                    • 가이드
                      1. 메인에 TouchableOpacity 버튼을 두고 navigate를 연결시키면 이동이 가능하겠죠?
                      2. Stack.screen에 AboutPage.js를 연결시켜야합니다!
                      3. 소개 페이지 상태바의 디테일함을 주목하세요! 흰색이네요 🙂
                  • </aside>
                  • 따라서 여러분이 직접! 스택네비게이터에 AboutPage.js를 달아보구요! 하단 메인페이지의 상단에 다음과 같은 버튼을 만들어서 누르면 AboutPage로 가게끔 해주세요!
                    1. 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요
                  • 가이드
                      1. 찜 데이터 제공
                      <aside> 💡 나만의 꿀 팁 앱에서 찜 페이지란 쉽게 말해 팁 리스트 중에 선택한 팁들을 모아보는 페이지입니다. 이런 찜 페이지 자주 보셨죠?
                      • 즉, 다음 상태값으로 화면을 구성해주세요::: 리스트에 딕셔너리 두 개가 들어있는 데이터입니다.
                      </aside>
                      • [코드스니펫] 찜 상태 데이터
                      • const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }])
                    • 하지만 아직 데이터를 저장하고, 수정하고 등등의 데이터 관리 부분을 배우지 않았기 때문에 [코드스니펫] 찜 상태 데이터를 가지고 화면만 그려볼겁니다.
                      1. 파일의 위치와 이름
                      <aside> 💡 새로운 페이지를 만드는 중이니, 어디에 위치해야 할까요? 당연히 pages 폴더에 위치시켜야 겠죠?</aside>
                    • 또한 파일이름은 우리 LikePage.js 로 만들어 봅시다! ZzimPage는 좀...
                      1. 메인의 꿀팁찜 버튼을 누르면 찜 페이지로!
                      <aside> 💡 혼자만의 큰그림이 있었습니다. 메인페이지에 꿀팁찜 버튼이란걸 강의 초반부터 만들어 놓았었어요! 이 버튼을 누르면 찜 페이지로 가게끔 해봅시다!
                    • </aside>
                      1. Card.js 를 복붙해서 찜 페이지에서만 사용하는 LikeCard.js 만들기
                      <aside> 💡 찜 목록도 Card.js와 크게 다를건 없습니다. 기존 카드에 찜 삭제 버튼정도가 추가 되었다는 점?이 다르므로, Card.js 코드를 복붙해서 LikeCard.js를 만들어 봅시다</aside>
                    • 그런데! LikeCard.js에선 TouchableOpacity로 감싸는게 아닌! View 태그로 카드 전체를 감싸주도록 바꿔주세요!
                      1. 최종 폴더 & 파일 모습
                  HW. 3주차 숙제 해설
                  • 숙제 1: 어바웃 화면 페이지화 시키고 버튼 추가하기!
                    • [코드스니펫] StackNavigator.js(숙제1)
                    • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> </Stack.Navigator> ) } export default StackNavigator;
                    • [코드스니펫] AboutPage.js(숙제1)
                    • import React,{useEffect} from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' import { StatusBar } from 'expo-status-bar'; export default function AboutPage({navigation,route}){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" useEffect(()=>{ navigation.setOptions({ title:"소개 페이지", headerStyle: { backgroundColor: '#1F266A', shadowColor: "#1F266A", }, headerTintColor: "#fff", }) },[]) return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:20, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
                    • [코드스니펫] MainPage.js(숙제1)
                    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20, }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
                  • 숙제 2: 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요!
                    • [코드스니펫] StackNavigator.js(숙제2)
                    • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; import LikePage from '../pages/LikePage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> <Stack.Screen name="LikePage" component={LikePage}/> </Stack.Navigator> ) } export default StackNavigator;
                    • [코드스니펫] LikeCard.js(숙제2)
                    • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function LikeCard({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <View style={styles.card}> <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> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                    • [코드스니펫] MainPage.js(숙제2)
                    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> **<TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>** </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
                    • [코드스니펫] LikePage.js(숙제2)
                    • import React,{useState, useEffect} from 'react'; import {ScrollView, Text, StyleSheet} from 'react-native'; import LikeCard from '../components/LikeCard'; import Card from '../components/Card'; export default function LikePage({navigation,route}){ const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }]) useEffect(()=>{ navigation.setOptions({ title:'꿀팁 찜' }) }) return ( { tip.map((content,i)=>{ return() }) } ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#fff" } })01. 오늘 배울 것
                      • 오늘 배울 내용이 두 가지는 앞으로도 계속~ 반복 숙달합니다 🙂
                        1. 리액트 필수 지식 활용
                        2. Expo 기능 사용
                        3. 페이지 적용
                        </aside>
                      • 오늘은 앱다운 앱을 만드는 연습과 기술을 배울텐데요! 다음 순서대로 지금까지 배웠던 것을 복습 하면서 앱다운 앱 기능을 배우니 점점 더 여러분은 앱 개발에 숙달되는 모습을 볼 수 있습니다!
                      • <aside> 👉 지난 시간까지 자바스크립트 기초체력을 다지고 JSX로 화면을 그리는 방법 까지 배워봤습니다.
                      • [앱 필수 기초지식] 리액트 필수지식
                        • 컴포넌트(Component)
                        • 상태(State,useState)
                        • 속성(Props)
                        • useEffect
                        이렇게 네 가지입니다. 단어가 조금 어려워 보이지만, 재미있고 간단한 개념들입니다. 이 개념들을 통해 앱 코드를 체계적으로 관리할 수 있고 화면안에서 데이터를 자유자재로 관리할 수도 있습니다.리액트 네이티브(React Native)는 리액트(React.js) 기반으로 만들어진 앱 개발 기술이기 때문이에요! 겁먹지마시고 차근차근 다루어봅시다.
                      • </aside>
                      • 최소한의 리액트 개념을 배우는 이유는
                      • <aside> 👉 리액트 네이티브 앱을 만들기 위해서 알아야 하는 최소한의 리액트 개념을 배웁니다.
                      • [Expo 앱다운 앱기능] 앱다운 앱을 위해 사용할 리액트 네이티브와 Expo 기능들이렇게 앱 다운 앱이 되기위한 기능들을 공식문서를 보고 하나하나 적용해보는 시간을 갖습니다. 이 연습을 거치면 여러분들은 공식문서를 보며 필요한 앱 기능들을 여러분들 앱에 붙여나갈 수 있습니다!
                      • </aside>
                      • <aside> 👉 앱이라고 하면 보통 현재 보고 있는 내용을 친구들한테 공유하거나, 앱 안에 있는 링크를 누르면 바로 해당 링크로 화면이 전환되는 기능들이 들어 있습니다. 또한 배터리를 볼 수 있는 상태바 스타일도 앱 전체적인 스타일에 맞게 변경할 수도 있어야 할겁니다.
                      • [앱 페이지 적용] 앱에 페이지 기능 넣기!그래서 앱안에 여러 페이지를 두고 버튼을 눌러 이동할 수 있는 기능을 달아봅니다. 본격적으로 거의 온전한 앱의 모습을 띄게 됩니다!
                      • </aside>
                      • <aside> 👉 앱에 페이지가 하나라면 너무 단조롭고 볼게 없는 앱이 되겠죠?
                      02. [함께해보기] 나만의 꿀팁 앱 상세 화면
                      • [실습 ✍️ ] 나만의 꿀팁 상세 페이지가될 아래의 화면을 만들어보세요
                      • 퀴즈 가이드
                        • [현재 코드 상황]
                          • App.js
                          • import React from 'react' import MainPage from './pages/MainPage'; import AboutPage from './pages/AboutPage'; export default function App(){ // return (<MainPage/>) return (<AboutPage/>) }
                          • MainPage.js
                          • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { 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> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, 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", } });
                          • AboutPage.js
                          • import React from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' export default function AboutPage(){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:100, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
                          • 폴더구조
                        • DetailPage.js를 pages 폴더에 만들어 넣기</aside>
                        • <aside> 👉 저번 1주차 마무리 시간에, pages 폴더를 만들고 AboutPage.js 파일을 만들어 넣었던 기억이 나시나요? 그때랑 동일하게 pages 폴더안에 DetailPage.js 파일을 만들고 App.js에서 return 부분에 DetailPage.js를 넣어주세요 다음과 같이요!
                        • 문제 디테일 화면을 위한 데이터딕셔너리 형태로 키값에 접근해서 값을 꺼내 사용해도 되고! 문자열들 그대로 복사해서 Text 태그에 넣어 사용해도 됩니다!
                          • [코드스니펫] 상세 화면에서 사용될 데이터
                          • const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }
                        • </aside>
                        • <aside> 👉 화면에 나타낼 꿀 팁은 다음 데이터를 사용하세요.
                      • 함께 해보기</aside>
                        • [코드스니펫] DetailPage.js 화면
                        • import React from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage() { const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" } const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                      • <aside> 👉 DetailPage를 확인하려면 App.js를 좀 바꿔야 겠죠? 어떻게 바꿔야 할까요?
                      03**.** [앱 필수 기초지식] 컴포넌트, 속성, 상태, useEffect
                      • 공부 할 리액트 필수 지식<aside> 👉 다시 떠올리자면, 리액트 네이티브는 리액트(React.js) 라이브러리 기반으로 만들어진 프레임워크 입니다. 그렇기 때문에 기본적인 구조는 리액트를 닯아 있습니다.1) 컴포넌트(Component) : 정해진 엘리먼트들(요소)을 사용하여 만든 화면의 일부분 2) 상태(State) : 컴포넌트에서 데이터를 유지하고 관리하기 위한 유일한 방법 == 그냥 사용할 데이터! 3) 속성(Props) : 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하는 방식 == 그냥 데이터 전달! 4) useEffect : 화면에 컴포넌트가 그려지면 처음 실행해야 하는 함수들을 모아두는 곳</aside>
                      • 네 가지가 있습니다. 워워 어렵지 않아요. 이것도 정해진 규칙과 개념일 뿐! 사용방법 숙지!만 하면 그만이랍니다!
                      • 대표적으로
                      • 컴포넌트(Component)</aside><aside> 👉 컴포넌트는 즉, 화면의 모든 부분입니다. 그리고 컴포넌트란 App.js의 큰 App 함수처럼, 코드 전체를 감싸고 있는 함수를 뜻하기도 합니다.</aside>이렇게 컴포넌트로 생각하고 앱을 개발해 나아간다면, 코드 재사용이 용이 해집니다.실제 우리 예제에 어떻게 적용할 수 있는지. 확인해보겠습니다.
                      • </aside>
                      • 코드 재사용이란 어려운 용어는 아니고, 버튼 하나를 만들었을 때 이 버튼 코드를 여러 페이지에서 사용 가능하다는 뜻입니다.
                      • <aside> 👉 버튼 하나가 컴포넌트가 될 수 있고, 버튼을 모아둔 영역이 컴포넌트가 될 수 있습니다.
                      • 따라서 App.js를 App 컴포넌트라고도 부를 수 있습니다.
                      • <aside> 👉 컴포넌트는 UI의 요소, 요소를 재사용 가능한 부분으로 조각내서 운영하는 기법입니다. 리액트 기반으로 만들어진 페이스북 웹사이트는 운영되는 컴포넌트가 수 만가지라고 합니다
                      • [실습 ✍️ ] 메인화면 컴포넌트화 해보기그럼 다음 코드를 Card.js에 넣어주세요! 어디서 많이 본 코드입니다 그쵸?<aside> 🚧 마찬가지로 MainPage.js 화면을 확인하려면 App.js를 바꿔야 겠죠? return (<MainPage/>) 으로요!
                        • [코드스니펫] Card.js
                        • import React from "react" import {View,Text,Image,StyleSheet} from "react-native"; //비구조 할당 방식으로 넘긴 속성 데이터를 꺼내 사용함 export default function Card({content}) { return (<View style={styles.card}> <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>) } const styles = StyleSheet.create({ 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", } })
                        <aside> 👉 맞습니다! MainPage.js에서 이미지위에 텍스트가 있던, 한 줄에 세 개씩 나열 되었던 그 카드 버튼입니다!.</aside>
                        • [코드스니펫] MainPage.js
                        • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; **import Card from '../components/Card';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (**<Card content={content} key={i}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                        <aside> 👉 MainPage.js에 있던 코드들을 Card.js로 옮겼습니다. 이렇게 옮기게 되면, 추후에 MainPage.js가 아닌 다른 페이지(페이지 또한 컴포넌트로 볼 수 있습니다) 또는 컴포넌트에서 Card.js를 사용할 수 있습니다.</aside>
                        • MainPage.js
                        • Card.js
                        <aside> 👉 미리 말씀드리자면 MainPage.js에서 Card.js로 데이터를 넘기고 있는 모습입니다! 이를 속성을 넘긴다! 라고 보통 일컫는데 이는 다음 챕터에서 더 자세히 다루도록 하겠습니다!
                      • </aside>
                      • 그런데 약간 생소한 부분이 있습니다 다음 두 부분인데요!
                      • 그럼 MainPage.js는 다음과 같이 변경해주세요
                      • </aside>
                      • </aside>
                      • <aside> 👉 Components 폴더 안에 Card.js라는 파일을 만듭니다. 보통 컴포넌트들은 components 폴더에 모아두면서 관리하곤 합니다.
                      • 속성(Props)<Text> 태그엔 numberOfLines이란 속성이 있었습니다. 말줌임표 효과를 주었죠! <Image> 태그엔 resizeMode란 속성이 있었습니다. 이미지가 영역을 차지하는 방식을 나타냈죠.</aside>예를 들어 Card를 사용하는 곳에서<Card image={'이미지 주소'}/> 라고 속성을 써 넣으면, 이 <Card/>는 image라는 속성이름에 {'이미지 주소'} 값을 을 갖게 됩니다. 즉 키와 벨류의 값을 갖게 됩니다 근데 이게 무슨 의미가 있을까요?</aside>
                      • 의미가 있습니다. 속성을 부여받은 컴포넌트에서 해당 속성 값을 받아서 사용 할 수 있거든요! 다음과 같이 말이죠!
                      • <aside> 👉 우리가 만든 <Card/> 컴포넌트에도 속성이 존재합니다. 우리가 부여 하면 부여하는 대로 속성이 되는 겁니다.
                      • 이 모든 속성들은 공식 문서에 나와 있고, 해당 엘리먼트들이 태어날 때부터 가지고 있는 속성이였습니다.
                      • <aside> 👉 속성은 쉽게 생각해서 컴포넌트에 데이터를 전달한다는 것이다. 그 전달 모습은 키와 벨류의 형태입니다.
                      • [확인해보기👀 ]</aside>
                        • MainPage.js 에서의 Card.js 컴포넌트
                        <aside> 👉 어렵지 않은 규칙이 숨어 있습니다!
                        1. 컴포넌트에 속성(데이터)을 부여해줘서 전달할땐, 키와 벨류(content={content}) 형태로 전달해줘야 할 것
                        2. 컴포넌트를 반복문 돌릴땐, 컴포넌트마다 고유하다는 것을 표현하기 위해, map에서 나오는 인덱스(i)를 key = {i} 속성 전달 형태로 꼭 넣을것! 입니다.
                        </aside>
                        • Card.js 에서의 속성 값 내려 받기
                        <aside> 👉 오랜만에 복습겸 나온 비구조 할당 방식 의 모습입니다. MainPage.js에서 넘겨준 속성은 실제 받게되는 컴포넌트에서 정말 딕셔너리 데이터를 받았다! 라고 생각하면 됩니다.이 방식으로 넘겨준 키값을 {키,키,키} 비구조 할당 방식으로 바로 꺼내서 사용하면 됩니다!
                      • </aside>
                      • 때문에, 비구조 할당 방식이 뭐였죠? 딕셔너리에서 키값을 바로 취해서 변수로써 함수안에서 즉시 사용할 수 있는 방식이였죠?
                      • <aside> 👉 MainPage.js에서 방금전 컴포넌트화! 했던 Card.js를 우린 이렇게 사용했습니다.
                      • 상태(State, useState)<aside> 👉 컴포넌트마다 데이터를 보유하고 관리 할 수 있습니다. 데이터라고 불러도 되지만, 리액트에서는 컴포넌트에서 보유/관리 되는 데이터를 **상태**라 부릅니다.사용방법은 실습을 통해 알아보도록 하겠습니다!
                      • </aside>
                      • 리액트에서 상태(state)는 리액트 라이브러리에서 제공해주는 useState로 생성하고 setState 함수로 정/변경 할 수 있습니다.
                      • useState, 그렇다면 왜 중요할까?여러분이 만드는 화면은 데이터에 따라 변경됩니다 또 아무 데이터가 아니라 이 상태(state)로 관리되는 데이터가 변경되면 화면이 바뀝니다.</aside><aside> 👉 당시 개발자들끼리 이 상태와 컴포넌트의 관계를 공식으로 표현한 글을 보고 대단하다!! 했던 기억이 있는데, 여러분들은 어떻게 느끼실지 궁금하네요 ㅎㅎ무슨말인지 아직 잘 와닿지 않으시죠? 다른건 잘 와닿지 않아도 state는 컴포넌트에서 관리되는 상태만 기억하시면 충분합니다 이 개념은 이번 강의에서 개념을 다 배운 뒤, 다음 6 강에서 실습 하며 직접 확인할 수 있습니다
                      • </aside>
                      • 저 공식은 즉, 사용자 화면(UI)는 컴포넌트(component)에 어떤 데이터(state)가 주입되고 변경되냐에 따라 변화된다를 뜻합니다
                      • UI = component(state)
                      • 모르셔도 되지만 꽤 유의미한 공식이 있습니다
                      • <aside> 👉 리액트는 특이한 점이 있습니다.
                      • 화면이 그려진다음 가장 먼저 실행되는 함수, useEffect화면이 그려진 다음 가장 먼저 실행되는 함수로 사용방법이 간단합니다 형식만 알고 있으면 충분합니다
                        **useEffect(()=>{**
                        
                        	...화면이 그려진 다음 가장 먼저 실행되야 할 코드 작성 공간
                        
                        **},[])**
                        
                        <aside> 👉 useEffect(()=>{ },[])</aside>
                      • 이 안에서, 화면이 그려진다음 실행시키고 싶은 함수를 작성한다면 가장먼저 실행이 됩니다. useEffect 또한 어떤 역할을 하는지만 알고! 바로 실습을 통해 확인해보도록 하겠습니다
                      • </aside>
                      • <aside> 👉 useEffect는 너무 간단하고 유용하게 쓰이는 리액트 기본 제공 함수입니다.
                      • useEffect, 그렇다면 왜 중요할까?
                        1. 화면이 그려진다
                        2. useEffect가 데이터를 준비한다
                        3. 상태 데이터가 업데이트 되었으니 화면이 다시 그려진다
                        화면이 그려진 다음, 서버에게 필요한 데이터를 요청하여 받은 후, 화면을 다시그릴때 주로 사용되는데요! 아주 일반적인 패턴이므로 여러분들도 금방 이해하고 익숙해지게 됩니다!
                      • </aside>
                      • <aside> 👉 보통 useEffect는 데이터를 준비할 때 사용합니다 데이터를 준비한다는 것은, 데이터를 서버로부터 혹은 어디선가로부터 받은 후 상태(state)에 반영한다는 것을 뜻합니다. 이런순서로 말이죠
                      • [실습✍️ ] data.json을 상태(state)에 넣어서 관리해보기복습하실때 주석도 자세히 살펴보세요!
                        • [코드스니펫] MainPage.js
                        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //useState 사용법 //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수 //setState는 state를 변경시킬때 사용해야하는 함수 //모두 다 useState가 선물해줌 //useState()안에 전달되는 값은 state 초기값 const [state,setState] = useState([]) //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수 //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음 useEffect(()=>{ setState(data) },[]) // let tip = data.tip; //data.json 데이터는 state에 담기므로 상태에서 꺼내옴 let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                        <aside> 👉 그러나 실행을 하면 다음과 같은 오류 화면이 나옵니다
                        • 오류화면
                        <aside> 👉 그 이유는 이런 절차로 발생됩니다.
                        1. 화면이 그려진다
                        2. useEffect가 데이터를 state에 useState를 이용하여 업데이트한다
                        3. 상태(state)가 변경되었으니 화면이 다시 그려진다
                        여기서 1번에서 오류가 난겁니다. 화면이 가장 먼저 그려질때, 우리가 Card에 데이터를 넘기기위해 어떤일을 했나요?이런 오류는 우리가 지금 배운 개념들, 상태, 컴포넌트, useEffect를 이용하여 로딩화면을 만들어줌으로써 해결이 가능합니다!
                      • </aside>
                      • 처음부터 데이터가 없어서 , 꺼내올수 없는 오류
                      • <aside> 👉 상태(state)에서 tip 키 값에 접근하여 데이터를 꺼내 map 즉, 반복문을 돌리려했습니다. 그런데 state에 데이터가 처음부터 있었나요? 없었죠? useEffect를 거쳐야 state에 데이터가 담기는데 아직 없는 상태라 오류가 나는 겁니다.
                      • </aside>
                      • </aside>
                      • </aside>
                      • <aside> 👉 지금 꿀팁을 data.json 파일에서 꺼내 바로 JSX 문법에서 사용하고 있습니다. 이를 다음과 같이 상태에 저장한 다음 사용하도록 변경해보도록 하겠습니다.
                      04. [앱 필수 기초지식 응용] 로딩화면, 카테고리 기능, 상태 바
                      • 앱 운영에 필요한 로딩화면이때 우린 로딩화면을 만들어줌으로써 쉽게 해결이 가능합니다. 컴포넌트 폴더에 Loading.js 파일을 만들어주고 다음 코드를 넣어주세요
                        • [코드스니펫] Loading.js
                        • import React from 'react'; import {View,Text,StyleSheet} from 'react-native'; export default function Loading(){ return(<View style={styles.container}><Text style={styles.title}>준비중입니다...</Text></View>) } const styles = StyleSheet.create({ container: { //앱의 배경 색 flex:1, justifyContent:'center', alignItems:'center', backgroundColor: '#fdc453', }, title: { fontSize:20, fontWeight:'700' } })
                        <aside> 👉 그리고 MainPage.js 다음 코드 스니펫을 넣어주세요
                        • [코드스니펫] MainPage.js
                        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 const [state,setState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. **const [ready,setReady] = useState(true)** useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 **setTimeout(()=>{ setState(data) setReady(false) },1000)** },[]) // let tip = data.tip; let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" **//처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐)** return **ready ? <Loading/>** : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                        • 적용 모습
                        <aside> 👉 ready 라는 새로운 상태값이 추가되었습니다. 즉, 상태값은 컴포넌트안에 여러개가 될 수 있습니다.즉, 이런 순서로 진행된거에요!
                        1. ready 값이 true이므로 return 구문에서 ? 물음표 바로 뒤의 Loading 컴포넌트가 화면에 그려짐
                        2. 화면이 그려지고 난다음, 1초 이따가 상태값들이 채워지고 변경됨
                        3. ready 상태 값이 false가 됨
                        4. 상태값이 변경되었으므로 화면이 다시 그려짐
                        5. ready 값이 false 이므로 return 구문에서 : 콜론 뒤의 MainPage 컴포넌트가 화면에 그려짐
                        차근차근 순서따라 반복해서 보면 별거아닙니다. 정해진 규칙에 따라 우린 코드를 적용했고 정해진 규칙에 따라 코드가 실행되고 있는 거에요!
                      • </aside>
                      • useEffect안에 setTimeout이라는 함수가 존재합니다. , 뒤에 있는 숫자 (1000 === 1초) 만큼, 지연됐다가 안에 있는 코드가 실행되는 지연 함수입니다.
                      • </aside>
                      • </aside>
                      • <aside> 👉 5장에서 오류가 났던 것 처럼 화면이 그려질때 준비된 데이터가 없다!? 이러한 상황은 아주 빈번합니다.
                      • 카테고리 버튼상태값이 바뀌면 화면이 다시 그려진다! 라는 개념을 염두하고 한번 만들어보겠습니다!
                      • </aside>
                      • <aside> 👉 우리에게 이런 버튼이 있었습니다. 카테고리 버튼인데요! 이 버튼은 장식이아니라, 실제 누르면 하단의 데이터가 카테고리에 맞게 다시 정렬되는 기능이 의도되어 있습니다.
                      • [실습✍️ ] 카테고리 기능 넣기그리곤 함수가 필요합니다. 우리가 만든, 생활 재테크 반려견 버튼에 연결할 함수인데요!? 이 함수는 카테고리에 따라 카테고리 상태데이터를 새롭게 구성해주는 기능을 합니다.
                        • [코드스니펫] MainPage.js
                        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 **const [state,setState] = useState([])** //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 **const [cateState,setCateState] = useState([])** //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 **let tip = data.tip; setState(tip) setCateState(tip) setReady(false)** },1000) },[]) **const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } }** let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> **<TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>** <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, **middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 },** 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" }, **middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" },** cardContainer: { marginTop:10, marginLeft:10 }, });
                        • 제태크를 눌렀을때의 모습
                      • </aside>
                      • <aside> 👉 카테고리 기능을 위해선 먼저 카테고리 상태가 필요합니다. 카테고리에 맞는 꿀팁들을 지속적으로 저장하고 관리할 상태 그릇이죠!
                      • Expo에서 제공해주는 앱다운 앱 기능들</aside>
                      • <aside> 💡 Expo SDK. 일명 Expo에서 제공해주는 앱 기능 도구들을 개발할 때 여기서 확인 할 수 있습니다. (링크) 필요한 기능들이 있는지 목록을 쭉 보고, 원하는 기능을 선택해서 적용하면 끝!
                      • 상태 바란?</aside><aside> 💡 배터리가 충분한지, 몇시인지 매일 보시죠? 이부분도 우리가 처리를 할 수 있습니다.
                      • </aside>
                      • <aside> 💡 앱이 앱에 따라 모바일 맨 위 상태 바가 변하는 앱이 있습니다. 상태바란 이런 겁니다. 여러분들 매일 같이 보는 부분이에요!
                      • StatusBar
                        • 본격적으로 라이브러리 설치 시작!</aside><aside> 👉 터미널 우측 버튼중에 분할이란 버튼이 있습니다.</aside>
                        • 여러분들 터미널 왼편은 서버를 켜고 끄고를 담당하고(expo start) 터미널 우측은 필요한 라이브러리들을 설치할 때 명령어를 치는 장소로 분할해서 사용하면 편리합니다!
                        • <aside> 👉 이제 본격적으로 라이브러리들을 설치합니다. vscode에서 터미널을 이렇게 변경해주세요
                        • [코드스니펫] Expo 상태 바 설치
                        • expo install expo-status-bar
                        • [실습 ✍️] 적용적용 모습을 살펴볼까요?
                          • [코드스니펫] MainPage에 StatusBar 설치
                          • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; **import { StatusBar } from 'expo-status-bar';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> **<StatusBar style="black" />** <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                          <aside> 💡 style이 light일 때와, black일 때가 다릅니다. 우린 앱 화면 배경색을 검은색으로 설정했기 떄문에, 상태바를 black으로 하면 보이지 않습니다.Status Bar 공식문서
                          • black 일때
                          • light 일때
                        • </aside>
                        • 상태 바 속성은 공식문서에 다양하게 존재하니, 살펴보면서 앱에 적합한 상태 바를 적용해보세요!
                        • </aside>
                        • <aside> 💡 컴포넌트마다 다르게 적용할 수도 있고, 앱 전체에 공통적으로 적용할 수 있습니다. StatusBar를 컴포넌트 각각 다르게 둘 수 있습니다 우린 MainPage.js에 달아볼거에요!
                      05**. [앱** 페이지 적용**] 네비게이터 사용하기**
                      • 네비게이션이란?<aside> 👉 앱에 페이지 개념을 입혀주고! 웹 사이트를 이용하듯, 앱에서 여러분들이 만든 컴포넌트들을 페이지화 시켜주고, 해당 페이지끼리 이동을 가능하게 해주는 라이브러리입니다가령 다음 화면과 같이, 앱 하단에 탭 버튼을 두고 해당 버튼을 누르면 연결되어 있는 페이지로 바로바로 이동할 수 있게 해주는 기능(열 앱에서 많이 보셨죠?)도 가지고 있습니다.</aside>
                      • 따라서 이번시간에 같이 공부한 다음 틈틈히 다른 기능들도 적용해보세요!
                      • 이번 강의에선 앱 내의 페이지 구성 및 이동까지 직접 적용해볼 텐데요!, 이 라이브러리는 다양한 기능들을 가지고 있습니다.
                      • <aside> 💡 결국 이 라이브러리도 Expo에서 지원하고 있는 도구로써, 앱을 만들기 위해 사용 중인 Expo와 궁합이 아주 잘 맞습니다.
                      • </aside>
                      • <aside> 💡 물론 이것 또한 쉽게 페이징을 도와주는 외부 라이브러리를 가져다가 사용합니다. react-navigation 공식문서 보러가기 —> (링크)
                      • </aside>
                      • 현재 갖추고 있는 페이지 구성 확인!</aside>
                      • <aside> 💡 페이지 구성은 보통 pages 폴더 안에 생성해둔 컴포넌트로 구성합니다. 우리는 이미, MainPage.js 메인 페이지 DetailPage.js 상세 화면 페이지 그리고 숙제로 만든 소개화면인 AboutPage.js 까지 총 3 페이지를 가지고 있습니다.
                      • 기본 설치 코드우리에게 필요한 것이라고 했지만, 거의 대부분의 앱에 적용이 되어 있는 것들만 가져왔습니다.아래 명령어를 차례차례 터미널에 넣어 실행해주세요 가장 마지막줄은 꽤 깁니다. 이렇게 여러 라이브러리들을 띄어쓰기로 연결해서 한번에 설치 할 수도 있습니다.
                        • [코드스니펫] 네비게이션 설치 코드
                        • yarn add @react-navigation/native
                        • [코드스니펫] 네비게이션 추가 설치코드
                        • expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
                      • </aside>
                      • 아래 명령어들은 네비게이션을 사용하기 위해 필요한 기본 라이브러리들이구요! 우리가 곧 배울 스택 네비게이션와 탭 네비게이션 기능은 추가적으로 라이브러리를 또 설치 해줘야 합니다 😂
                      • <aside> 💡 리액트 네이티브에서 페이지 네비게이션을 구현하기란 조금 까다롭습니다. 그래서 딱 우리에게 필요한 것만 가져와 설치 하고, 적용해 나갈 계획입니다.
                      • 스택 네비게이션이란?</aside><aside> 💡 컴포넌트를 페이지화 시키는 스택 네비게이션은 다음과 같습니다.이렇게 만든 여러 페이지들을 책갈피 기능을 하는 스택 네비게이터에 모조리 등록시켜서, 언제든지 이 페이지 이동이 가능하게끔 해줍니다</aside>
                      • 페이지는 Stack.Screen 이라 부르며 책갈피는 Stack.Navigator라 부릅니다
                      • 우리가 페이지처럼 만든 컴포넌트를, 정말 페이지처럼 사용할 수 있게끔 페이지로 컴포넌트를 감싸 페이지로 만들어줍니다.
                      • <aside> 💡 스택 네비게이션은 컴포넌트에 페이지 기능을 부여해주고 컴포넌트에서 컴포넌트로 이동, 즉 페이지 이동을 가능하게 해줍니다
                      • createStackNavigator 사용해보기
                        • [코드스니펫] 스택 네비게이터 설치 코드
                        • yarn add @react-navigation/stack
                        • [실습 ✍️] 적용하기</aside>
                          • [코드스니펫] StackNavigator.js 스택 네비게이터
                          • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
                          <aside> 👉 워워 진정하세요! 위에서부터 차근차근 살펴볼거에요 🙂 언제나 우린 미니카 조립 설명서를 보면서 조립하듯, 공식 문서 내용 그대로 적용한다는 것 꼭 잊지마시고! 살펴보도록 하겠습니다.
                          • 💻스택 네비게이터 코드 분석
                            • 공식 문서 링크
                            • 적용 순서 1) 사용 준비
                            • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator();
                            • 적용 순서 2) 기본 틀
                            • //리액트의 모~든 파일은 컴포넌트라 생각하고 //페이지 기능을 해주는 모든 기능이 담겨 있는 컴포넌트를 만든다 생각하세요! const StackNavigator = () =>{ return ( /// 페이지 기능이 들어갈 곳 ) } export default StackNavigator;
                            • 적용 순서 3) 스크린 옵션
                            • //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
                            • 적용 순서 4) 페이지 연결
                            • {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/>
                          <aside> 👉 컴포넌트를 페이지화 했고, 페이지를 이동할 수 있는 네이게이션도 준비가 됐다면, 우리는 최상단 컴포넌트 즉 App.js에 네비게이션 기능을 달아야 합니다.</aside>
                          • [코드스니펫] App.js
                          • import React from 'react'; //이제 모든 페이지 컴포넌트들이 끼워져있는 책갈피를 메인에 둘예정이므로 //컴포넌트를 더이상 불러오지 않아도 됩니다. // import MainPage from './pages/MainPage'; // import DetailPage from './pages/DetailPage'; import { StatusBar } from 'expo-status-bar'; //메인에 세팅할 네비게이션 도구들을 가져옵니다. import {NavigationContainer} from '@react-navigation/native'; import StackNavigator from './navigation/StackNavigator' export default function App() { console.disableYellowBox = true; return ( <NavigationContainer> <StatusBar style="black" /> <StackNavigator/> </NavigationContainer>); }
                          • 스택 네비게이터 적용 후 MainPage 모습
                        • 즉, 앱 가장 최상위 코드에 네비게이션을 다는겁니다 그래야 앱 어디서든 원하는 페이지 이동이 가능할테니까요!
                        • </aside>
                        • <aside> 👉 navigation 폴더 하나를 만들고 StackNavigator.js 파일을 만들어주세요 그리고 StackNavigator 안에 다음 코드를 넣어주세요
                      • 페이지 헤더 수정</aside><aside> 💡 그 이유는 StackNavigator에서 현재 헤더에 대한 스타일과 페이지에 헤더의 제목까지 결정해주고 있기 때문입니다.
                        • 스택 네비게이터의 헤더 스타일 부분 코드
                        • <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTintColor: "#fff", headerBackTitleVisible: false }} > {/* name에 해당 하는 부분이 페이지의 타이틀이 됩니다.*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
                        <aside> 💡 따라서 헤더 스타일을 적절히 바꾸기 위해 다음과 같이 옵션 부분의 코드를 수정해보도록 하겠습니다.
                        • [코드스니펫] StackNavigator.js
                        • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
                        • [코드스니펫] MainPage.js 에서 title 삭제!
                        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                        • StackNavigator 옵션 코드 수정 후 모습
                        <aside> 💡 그래도 위에 페이지 제목으로 MainPage가 되는 모습은 뭔가 또 수정이 필요해보입니다. 이를 위해선 다음 스택 네비게이션 페이지 이동 강의에서 살펴보도록 하겠습니다
                      • </aside>
                      • </aside>
                      • </aside>
                      • <aside> 💡 그런데 현재 MainPage 화면은 다음과 같이 상단의 모습이 약간 이상해 보입니다.
                      • 페이지 이동하기일단 페이지를 이동시키려면, 책갈피가 페이지들에게 부여해준 페이지 이동 기능을 사용해야 합니다.</aside><aside> 💡 일단 먼저 구조를 보여드렸습니다. 물론 사용을 해봐야 감이 오겠죠?같이 해보죠!
                        • [실습 ✍️] 데이터 없이 페이지 이동하기
                          • [코드스니펫] MainPage.js
                          • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (**<Card content={content} key={i} navigation={navigation}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                          • [코드스니펫] Card.js
                          • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 **export default function Card({content,navigation}){** return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage')}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                        • navigation.navigate("DetailPage")
                        • [실습 ✍️] 데이터 가지고 페이지 이동하기</aside><aside> 💡 그럼 Card에서 DetailPage로 이동할 때, MainPage로 부터 넘겨받은 content도 넘겨볼까요?</aside>
                          • [코드스니펫] Card.js
                          • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function Card({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage',**content**)}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                          • [코드스니펫] DetailPage.js
                          • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage({navigation,route}) { //초기 컴포넌트의 상태값을 설정 //state, setState 뿐 아니라 이름을 마음대로 지정할 수 있음! const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) //Card.js에서 navigation.navigate 함수를 쓸때 두번째 인자로 content를 넘겨줬죠? //content는 딕셔너리 그 자체였으므로 route.params에 고대~로 남겨옵니다. //즉, route.params 는 content죠! navigation.setOptions({ //setOptions로 페이지 타이틀도 지정 가능하고 title:route.params.title, //StackNavigator에서 작성했던 옵션을 다시 수정할 수도 있습니다. headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                          • 이동한 디테일 페이지 모습
                          <aside> 💡 건네 받은 값을 꺼낼 땐, 다음과 같이 책갈피가 navigation과 추가적으로 건네준 route에서 꺼내 확인 할 수 있습니다.</aside>
                          • route에 담겨져 오는 데이터 콘솔에서 직접 확인
                          • DetailPage에서 상태값을 초기에 설정한 이유</aside><aside> 💡 그 이유는 컴포넌트가 화면에 그려지는 순서에 있습니다. 이건 지금 단계에선 조금 과한감이 있어서 간략히만 설명해보면 다음과 같습니다.
                            1. DetailPage 컴포넌트가 useState에 들어 있는 
                               데이터 가지고 화면에 그려짐(return 함수실행)
                            2. 화면에 다 그려진후, useEffect 함수 실행
                            3. useEffect에서 상태값 변경 이벤트가 실행되면 변경된 데이터 가지고 다시 return 실행
                            4. 변경된 데이터를 가지고 화면에 DetailPage가 다시 그려짐.
                            
                            <aside> 💡 곰곰히 생각해보면 결국, 리액트 네이티브에서 화면이 변경되는 시점은 컴포넌트의 상태값이 변경될 때니까요!또는! 우리가 한번 배웠떤 Loading.js를 이용하여 데이터가 준비가 되면 로딩 화면을 치우고, 본 화면을 보여준다던가!</aside>
                          • 여러분들 마음껏 화면 처리를 해주시면 됩니다!
                          • 이 개념은 어려울 수 있습니다 충분히! 따라서 처음에는, 어떠한 데이터를 보여주는 컴포넌트라면, 무조건 초기값을 의미없는 값이더라도 넣고 시작한다! 라고 생각하시면 편합니다!
                          • </aside>
                          • //초기 컴포넌트의 상태값을 설정 const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" })
                          • <aside> 💡 DetailPage 초반에 우린 이렇게 상태값을 설정해놨었습니다. 그 이유가 뭘까요? 심지어 이 상태값을 초기에 설정안하면 오류가 발생합니다. tip엔 아무것도 없다며...
                        • route.params 객체에 건네준 딕셔너리가 넘겨 있습니다!
                        • 왜냐하면 지금은 어떠한 카드를 눌러도 상세 페이지에서 동일한 데이터를 보게 되잖아요? tip 데이터를 고정해놨으니까요!
                        • navigation.navigate("Detail",**{** title**:** title **})**
                        • <aside> 💡 버튼 카드에서 사용한 함수에 아래와 같이 두 번째 인자로 딕셔너리를 넘겨주면, 우리는 이동 한 페이지에서 넘겨준 데이터를 받을 수 있습니다.
                      • </aside>
                      • 그럼 이제 Card.js에 페이지 이동 기능을 달아보겠습니다. 코드를 실행 한 다음 페이지를 이동해 보면 익숙한 뒤로 가기 버튼도 보일거에요!
                      • //navigation 객체가 가지고 있는 두 함수(setOptions와 navigate) //해당 페이지의 제목을 설정할 수 있음 navigation.setOptions({ title:'나만의 꿀팁' }) //Stack.screen에서 name 속성으로 정해준 이름을 지정해주면 해당 페이지로 이동하는 함수 navigation.navigate("DetailPage") //name 속성을 전달해주고, 두 번째 인자로 딕셔너리 데이터를 전달해주면, Detail 페이지에서 //두번째 인자로 전달된 딕셔너리 데이터를 route 딕셔너리로 로 받을 수 있음 navigation.navigate("DetailPage",**{ title: title }**) //전달받은 데이터를 받는 route 딕셔너리 //비구조 할당 방식으로 route에 params 객체 키로 연결되어 전달되는 데이터를 꺼내 사용 //navigate 함수로 전달되는 딕셔너리 데이터는 다음과 같은 모습이기 때문입니다. /* { route : { params :{ **title:title** } } } */ **const {** title**} = route.params;**
                      • Stack.screen에 등록된 모든 페이지 컴포넌트들은 navigation 와 route 라는 딕셔너리(객체)를 속성으로 넘겨받아 사용할 수 있습니다. 이 두 딕셔너리는 다음과 같은 기능을 갖습니다.
                      • <aside> 💡 Main 페이지(MainPage.js)에서 카드 버튼을 누르면(Card.js) 꿀팁 상세 페이지(DetailPage.js)로 이동합니다
                      06**. [Expo 앱다운 앱기능] 공유 기능과 외부 링크 추가**
                      • Share그런데 결과를 친구한테 공유하고 싶은 마음이 들수도 있겠네요 결과를 공유해주고, 앱다운로드 주소까지 전달해준다면 여러분이 만든 앱 홍보하기가 수월하겠죠?
                      • </aside>
                      • <aside> 👉 디테일 페이지까지 이동해서 도달했다면 여러분은 앱 개발자!
                      • Share 적용해보기
                        • 준비!
                          • 설치 할 라이브러리 없이, react-native에서 기본적으로 제공해주는 공유 기능을 사용합니다.
                          import { Share } from "react-native";
                          
                          • 그리고 우린 다음과 같은 공유 버튼을 디테일 페이지 하단에 생성하고 기능을 만들꺼에요! 한번 잠깐 영상을 멈추고 기능은 생각 말고 다음 버튼을 추가한 화면만 그려보세요! 복습 복습
                          • 버튼 추가 디테일 페이지 모습
                        • [실습 ✍️] 적용</aside>
                          • [코드스니펫] DetailPage.js
                          • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { **Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); }** return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}**>팁 공유하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:100, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                          • 공유하는 모습
                        • <aside> 💡 share 함수를 만들고 거기에 react-native가 기본적으로 제공해주는 Share 함수를 사용하여 간단히 공유를 할 수 있습니다
                        • 공유 모습
                      • Linking이번엔 앱에서 외부 링크를 여는 방법에 대해 배워봅니다. 버튼을 누르면, 버튼에 연결 시킨 기능을 통해 외부 링크를 핸드폰에 있는 기본 브라우저로 열어봅니다.
                      • </aside>
                      • <aside> 👉 디테일 페이지에 있는 내용들이 사실 어디에서 가져온거라면? 출처를 남겨야 할겁니다. 그리고 그 출처로 바로가기 버튼정도도 있으면 여러분들이 만드는 다양한 플랫폼들을 서로 연결 시킬 수 있겠죠?
                      • Linking 적용해보기
                        • 준비!
                          • DetailPage에 버튼을 다음과 같이 추가해주세요
                          • 링크 버튼 추가 된 DetailPage 모습
                          • expo 에서 제공해주는 도구를 설치 한다음, 해당 도구를 상단에 가져와 준비해야 합니다
                          expo install expo-linking
                          
                          import * as Linking from 'expo-linking';
                          
                        • [실습 ✍️] 적용
                          • [코드스니펫] DetailPage.js
                          • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; import * as Linking from 'expo-linking'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); } const link = () => { Linking.openURL("<https://spartacodingclub.kr>") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}>팁 공유하기 link()}>외부 링크 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:90, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                      07**. 3주차 끝 & 숙제 설명**
                        1. 어바웃 화면 페이지화 시키고 버튼 추가하기!
                        <aside> 👉 지난번 숙제로 만든 AboutPage.js를 아직 우린 스택 네비게이터에 연결하지 않았습니다.추가적으로 외부 링크 걸기까지 배웠으니, 여러분 인스타나 블로그 주소 혹은 아무주소를 AboutPage 하단 버튼에 연결해주세요!
                        • 만들 화면
                        • 가이드
                          1. 메인에 TouchableOpacity 버튼을 두고 navigate를 연결시키면 이동이 가능하겠죠?
                          2. Stack.screen에 AboutPage.js를 연결시켜야합니다!
                          3. 소개 페이지 상태바의 디테일함을 주목하세요! 흰색이네요 🙂
                      • </aside>
                      • 따라서 여러분이 직접! 스택네비게이터에 AboutPage.js를 달아보구요! 하단 메인페이지의 상단에 다음과 같은 버튼을 만들어서 누르면 AboutPage로 가게끔 해주세요!
                        1. 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요
                      • 가이드
                          1. 찜 데이터 제공
                          <aside> 💡 나만의 꿀 팁 앱에서 찜 페이지란 쉽게 말해 팁 리스트 중에 선택한 팁들을 모아보는 페이지입니다. 이런 찜 페이지 자주 보셨죠?
                          • 즉, 다음 상태값으로 화면을 구성해주세요::: 리스트에 딕셔너리 두 개가 들어있는 데이터입니다.
                          </aside>
                          • [코드스니펫] 찜 상태 데이터
                          • const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }])
                        • 하지만 아직 데이터를 저장하고, 수정하고 등등의 데이터 관리 부분을 배우지 않았기 때문에 [코드스니펫] 찜 상태 데이터를 가지고 화면만 그려볼겁니다.
                          1. 파일의 위치와 이름
                          <aside> 💡 새로운 페이지를 만드는 중이니, 어디에 위치해야 할까요? 당연히 pages 폴더에 위치시켜야 겠죠?</aside>
                        • 또한 파일이름은 우리 LikePage.js 로 만들어 봅시다! ZzimPage는 좀...
                          1. 메인의 꿀팁찜 버튼을 누르면 찜 페이지로!
                          <aside> 💡 혼자만의 큰그림이 있었습니다. 메인페이지에 꿀팁찜 버튼이란걸 강의 초반부터 만들어 놓았었어요! 이 버튼을 누르면 찜 페이지로 가게끔 해봅시다!
                        • </aside>
                          1. Card.js 를 복붙해서 찜 페이지에서만 사용하는 LikeCard.js 만들기
                          <aside> 💡 찜 목록도 Card.js와 크게 다를건 없습니다. 기존 카드에 찜 삭제 버튼정도가 추가 되었다는 점?이 다르므로, Card.js 코드를 복붙해서 LikeCard.js를 만들어 봅시다</aside>
                        • 그런데! LikeCard.js에선 TouchableOpacity로 감싸는게 아닌! View 태그로 카드 전체를 감싸주도록 바꿔주세요!
                          1. 최종 폴더 & 파일 모습
                      HW. 3주차 숙제 해설
                      • 숙제 1: 어바웃 화면 페이지화 시키고 버튼 추가하기!
                        • [코드스니펫] StackNavigator.js(숙제1)
                        • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> </Stack.Navigator> ) } export default StackNavigator;
                        • [코드스니펫] AboutPage.js(숙제1)
                        • import React,{useEffect} from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' import { StatusBar } from 'expo-status-bar'; export default function AboutPage({navigation,route}){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" useEffect(()=>{ navigation.setOptions({ title:"소개 페이지", headerStyle: { backgroundColor: '#1F266A', shadowColor: "#1F266A", }, headerTintColor: "#fff", }) },[]) return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:20, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
                        • [코드스니펫] MainPage.js(숙제1)
                        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20, }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
                      • 숙제 2: 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요!
                        • [코드스니펫] StackNavigator.js(숙제2)
                        • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; import LikePage from '../pages/LikePage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> <Stack.Screen name="LikePage" component={LikePage}/> </Stack.Navigator> ) } export default StackNavigator;
                        • [코드스니펫] LikeCard.js(숙제2)
                        • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function LikeCard({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <View style={styles.card}> <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> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                        • [코드스니펫] MainPage.js(숙제2)
                        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> **<TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>** </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
                        • [코드스니펫] LikePage.js(숙제2)
                        • import React,{useState, useEffect} from 'react'; import {ScrollView, Text, StyleSheet} from 'react-native'; import LikeCard from '../components/LikeCard'; import Card from '../components/Card'; export default function LikePage({navigation,route}){ const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }]) useEffect(()=>{ navigation.setOptions({ title:'꿀팁 찜' }) }) return ( { tip.map((content,i)=>{ return() }) } ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#fff" } })01. 오늘 배울 것
                          • 오늘 배울 내용이 두 가지는 앞으로도 계속~ 반복 숙달합니다 🙂
                            1. 리액트 필수 지식 활용
                            2. Expo 기능 사용
                            3. 페이지 적용
                            </aside>
                          • 오늘은 앱다운 앱을 만드는 연습과 기술을 배울텐데요! 다음 순서대로 지금까지 배웠던 것을 복습 하면서 앱다운 앱 기능을 배우니 점점 더 여러분은 앱 개발에 숙달되는 모습을 볼 수 있습니다!
                          • <aside> 👉 지난 시간까지 자바스크립트 기초체력을 다지고 JSX로 화면을 그리는 방법 까지 배워봤습니다.
                          • [앱 필수 기초지식] 리액트 필수지식
                            • 컴포넌트(Component)
                            • 상태(State,useState)
                            • 속성(Props)
                            • useEffect
                            이렇게 네 가지입니다. 단어가 조금 어려워 보이지만, 재미있고 간단한 개념들입니다. 이 개념들을 통해 앱 코드를 체계적으로 관리할 수 있고 화면안에서 데이터를 자유자재로 관리할 수도 있습니다.리액트 네이티브(React Native)는 리액트(React.js) 기반으로 만들어진 앱 개발 기술이기 때문이에요! 겁먹지마시고 차근차근 다루어봅시다.
                          • </aside>
                          • 최소한의 리액트 개념을 배우는 이유는
                          • <aside> 👉 리액트 네이티브 앱을 만들기 위해서 알아야 하는 최소한의 리액트 개념을 배웁니다.
                          • [Expo 앱다운 앱기능] 앱다운 앱을 위해 사용할 리액트 네이티브와 Expo 기능들이렇게 앱 다운 앱이 되기위한 기능들을 공식문서를 보고 하나하나 적용해보는 시간을 갖습니다. 이 연습을 거치면 여러분들은 공식문서를 보며 필요한 앱 기능들을 여러분들 앱에 붙여나갈 수 있습니다!
                          • </aside>
                          • <aside> 👉 앱이라고 하면 보통 현재 보고 있는 내용을 친구들한테 공유하거나, 앱 안에 있는 링크를 누르면 바로 해당 링크로 화면이 전환되는 기능들이 들어 있습니다. 또한 배터리를 볼 수 있는 상태바 스타일도 앱 전체적인 스타일에 맞게 변경할 수도 있어야 할겁니다.
                          • [앱 페이지 적용] 앱에 페이지 기능 넣기!그래서 앱안에 여러 페이지를 두고 버튼을 눌러 이동할 수 있는 기능을 달아봅니다. 본격적으로 거의 온전한 앱의 모습을 띄게 됩니다!
                          • </aside>
                          • <aside> 👉 앱에 페이지가 하나라면 너무 단조롭고 볼게 없는 앱이 되겠죠?
                          02. [함께해보기] 나만의 꿀팁 앱 상세 화면
                          • [실습 ✍️ ] 나만의 꿀팁 상세 페이지가될 아래의 화면을 만들어보세요
                          • 퀴즈 가이드
                            • [현재 코드 상황]
                              • App.js
                              • import React from 'react' import MainPage from './pages/MainPage'; import AboutPage from './pages/AboutPage'; export default function App(){ // return (<MainPage/>) return (<AboutPage/>) }
                              • MainPage.js
                              • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { 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> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, 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", } });
                              • AboutPage.js
                              • import React from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' export default function AboutPage(){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:100, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
                              • 폴더구조
                            • DetailPage.js를 pages 폴더에 만들어 넣기</aside>
                            • <aside> 👉 저번 1주차 마무리 시간에, pages 폴더를 만들고 AboutPage.js 파일을 만들어 넣었던 기억이 나시나요? 그때랑 동일하게 pages 폴더안에 DetailPage.js 파일을 만들고 App.js에서 return 부분에 DetailPage.js를 넣어주세요 다음과 같이요!
                            • 문제 디테일 화면을 위한 데이터딕셔너리 형태로 키값에 접근해서 값을 꺼내 사용해도 되고! 문자열들 그대로 복사해서 Text 태그에 넣어 사용해도 됩니다!
                              • [코드스니펫] 상세 화면에서 사용될 데이터
                              • const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }
                            • </aside>
                            • <aside> 👉 화면에 나타낼 꿀 팁은 다음 데이터를 사용하세요.
                          • 함께 해보기</aside>
                            • [코드스니펫] DetailPage.js 화면
                            • import React from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage() { const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" } const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                          • <aside> 👉 DetailPage를 확인하려면 App.js를 좀 바꿔야 겠죠? 어떻게 바꿔야 할까요?
                          03**.** [앱 필수 기초지식] 컴포넌트, 속성, 상태, useEffect
                          • 공부 할 리액트 필수 지식<aside> 👉 다시 떠올리자면, 리액트 네이티브는 리액트(React.js) 라이브러리 기반으로 만들어진 프레임워크 입니다. 그렇기 때문에 기본적인 구조는 리액트를 닯아 있습니다.1) 컴포넌트(Component) : 정해진 엘리먼트들(요소)을 사용하여 만든 화면의 일부분 2) 상태(State) : 컴포넌트에서 데이터를 유지하고 관리하기 위한 유일한 방법 == 그냥 사용할 데이터! 3) 속성(Props) : 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하는 방식 == 그냥 데이터 전달! 4) useEffect : 화면에 컴포넌트가 그려지면 처음 실행해야 하는 함수들을 모아두는 곳</aside>
                          • 네 가지가 있습니다. 워워 어렵지 않아요. 이것도 정해진 규칙과 개념일 뿐! 사용방법 숙지!만 하면 그만이랍니다!
                          • 대표적으로
                          • 컴포넌트(Component)</aside><aside> 👉 컴포넌트는 즉, 화면의 모든 부분입니다. 그리고 컴포넌트란 App.js의 큰 App 함수처럼, 코드 전체를 감싸고 있는 함수를 뜻하기도 합니다.</aside>이렇게 컴포넌트로 생각하고 앱을 개발해 나아간다면, 코드 재사용이 용이 해집니다.실제 우리 예제에 어떻게 적용할 수 있는지. 확인해보겠습니다.
                          • </aside>
                          • 코드 재사용이란 어려운 용어는 아니고, 버튼 하나를 만들었을 때 이 버튼 코드를 여러 페이지에서 사용 가능하다는 뜻입니다.
                          • <aside> 👉 버튼 하나가 컴포넌트가 될 수 있고, 버튼을 모아둔 영역이 컴포넌트가 될 수 있습니다.
                          • 따라서 App.js를 App 컴포넌트라고도 부를 수 있습니다.
                          • <aside> 👉 컴포넌트는 UI의 요소, 요소를 재사용 가능한 부분으로 조각내서 운영하는 기법입니다. 리액트 기반으로 만들어진 페이스북 웹사이트는 운영되는 컴포넌트가 수 만가지라고 합니다
                          • [실습 ✍️ ] 메인화면 컴포넌트화 해보기그럼 다음 코드를 Card.js에 넣어주세요! 어디서 많이 본 코드입니다 그쵸?<aside> 🚧 마찬가지로 MainPage.js 화면을 확인하려면 App.js를 바꿔야 겠죠? return (<MainPage/>) 으로요!
                            • [코드스니펫] Card.js
                            • import React from "react" import {View,Text,Image,StyleSheet} from "react-native"; //비구조 할당 방식으로 넘긴 속성 데이터를 꺼내 사용함 export default function Card({content}) { return (<View style={styles.card}> <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>) } const styles = StyleSheet.create({ 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", } })
                            <aside> 👉 맞습니다! MainPage.js에서 이미지위에 텍스트가 있던, 한 줄에 세 개씩 나열 되었던 그 카드 버튼입니다!.</aside>
                            • [코드스니펫] MainPage.js
                            • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; **import Card from '../components/Card';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (**<Card content={content} key={i}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                            <aside> 👉 MainPage.js에 있던 코드들을 Card.js로 옮겼습니다. 이렇게 옮기게 되면, 추후에 MainPage.js가 아닌 다른 페이지(페이지 또한 컴포넌트로 볼 수 있습니다) 또는 컴포넌트에서 Card.js를 사용할 수 있습니다.</aside>
                            • MainPage.js
                            • Card.js
                            <aside> 👉 미리 말씀드리자면 MainPage.js에서 Card.js로 데이터를 넘기고 있는 모습입니다! 이를 속성을 넘긴다! 라고 보통 일컫는데 이는 다음 챕터에서 더 자세히 다루도록 하겠습니다!
                          • </aside>
                          • 그런데 약간 생소한 부분이 있습니다 다음 두 부분인데요!
                          • 그럼 MainPage.js는 다음과 같이 변경해주세요
                          • </aside>
                          • </aside>
                          • <aside> 👉 Components 폴더 안에 Card.js라는 파일을 만듭니다. 보통 컴포넌트들은 components 폴더에 모아두면서 관리하곤 합니다.
                          • 속성(Props)<Text> 태그엔 numberOfLines이란 속성이 있었습니다. 말줌임표 효과를 주었죠! <Image> 태그엔 resizeMode란 속성이 있었습니다. 이미지가 영역을 차지하는 방식을 나타냈죠.</aside>예를 들어 Card를 사용하는 곳에서<Card image={'이미지 주소'}/> 라고 속성을 써 넣으면, 이 <Card/>는 image라는 속성이름에 {'이미지 주소'} 값을 을 갖게 됩니다. 즉 키와 벨류의 값을 갖게 됩니다 근데 이게 무슨 의미가 있을까요?</aside>
                          • 의미가 있습니다. 속성을 부여받은 컴포넌트에서 해당 속성 값을 받아서 사용 할 수 있거든요! 다음과 같이 말이죠!
                          • <aside> 👉 우리가 만든 <Card/> 컴포넌트에도 속성이 존재합니다. 우리가 부여 하면 부여하는 대로 속성이 되는 겁니다.
                          • 이 모든 속성들은 공식 문서에 나와 있고, 해당 엘리먼트들이 태어날 때부터 가지고 있는 속성이였습니다.
                          • <aside> 👉 속성은 쉽게 생각해서 컴포넌트에 데이터를 전달한다는 것이다. 그 전달 모습은 키와 벨류의 형태입니다.
                          • [확인해보기👀 ]</aside>
                            • MainPage.js 에서의 Card.js 컴포넌트
                            <aside> 👉 어렵지 않은 규칙이 숨어 있습니다!
                            1. 컴포넌트에 속성(데이터)을 부여해줘서 전달할땐, 키와 벨류(content={content}) 형태로 전달해줘야 할 것
                            2. 컴포넌트를 반복문 돌릴땐, 컴포넌트마다 고유하다는 것을 표현하기 위해, map에서 나오는 인덱스(i)를 key = {i} 속성 전달 형태로 꼭 넣을것! 입니다.
                            </aside>
                            • Card.js 에서의 속성 값 내려 받기
                            <aside> 👉 오랜만에 복습겸 나온 비구조 할당 방식 의 모습입니다. MainPage.js에서 넘겨준 속성은 실제 받게되는 컴포넌트에서 정말 딕셔너리 데이터를 받았다! 라고 생각하면 됩니다.이 방식으로 넘겨준 키값을 {키,키,키} 비구조 할당 방식으로 바로 꺼내서 사용하면 됩니다!
                          • </aside>
                          • 때문에, 비구조 할당 방식이 뭐였죠? 딕셔너리에서 키값을 바로 취해서 변수로써 함수안에서 즉시 사용할 수 있는 방식이였죠?
                          • <aside> 👉 MainPage.js에서 방금전 컴포넌트화! 했던 Card.js를 우린 이렇게 사용했습니다.
                          • 상태(State, useState)<aside> 👉 컴포넌트마다 데이터를 보유하고 관리 할 수 있습니다. 데이터라고 불러도 되지만, 리액트에서는 컴포넌트에서 보유/관리 되는 데이터를 **상태**라 부릅니다.사용방법은 실습을 통해 알아보도록 하겠습니다!
                          • </aside>
                          • 리액트에서 상태(state)는 리액트 라이브러리에서 제공해주는 useState로 생성하고 setState 함수로 정/변경 할 수 있습니다.
                          • useState, 그렇다면 왜 중요할까?여러분이 만드는 화면은 데이터에 따라 변경됩니다 또 아무 데이터가 아니라 이 상태(state)로 관리되는 데이터가 변경되면 화면이 바뀝니다.</aside><aside> 👉 당시 개발자들끼리 이 상태와 컴포넌트의 관계를 공식으로 표현한 글을 보고 대단하다!! 했던 기억이 있는데, 여러분들은 어떻게 느끼실지 궁금하네요 ㅎㅎ무슨말인지 아직 잘 와닿지 않으시죠? 다른건 잘 와닿지 않아도 state는 컴포넌트에서 관리되는 상태만 기억하시면 충분합니다 이 개념은 이번 강의에서 개념을 다 배운 뒤, 다음 6 강에서 실습 하며 직접 확인할 수 있습니다
                          • </aside>
                          • 저 공식은 즉, 사용자 화면(UI)는 컴포넌트(component)에 어떤 데이터(state)가 주입되고 변경되냐에 따라 변화된다를 뜻합니다
                          • UI = component(state)
                          • 모르셔도 되지만 꽤 유의미한 공식이 있습니다
                          • <aside> 👉 리액트는 특이한 점이 있습니다.
                          • 화면이 그려진다음 가장 먼저 실행되는 함수, useEffect화면이 그려진 다음 가장 먼저 실행되는 함수로 사용방법이 간단합니다 형식만 알고 있으면 충분합니다
                            **useEffect(()=>{**
                            
                            	...화면이 그려진 다음 가장 먼저 실행되야 할 코드 작성 공간
                            
                            **},[])**
                            
                            <aside> 👉 useEffect(()=>{ },[])</aside>
                          • 이 안에서, 화면이 그려진다음 실행시키고 싶은 함수를 작성한다면 가장먼저 실행이 됩니다. useEffect 또한 어떤 역할을 하는지만 알고! 바로 실습을 통해 확인해보도록 하겠습니다
                          • </aside>
                          • <aside> 👉 useEffect는 너무 간단하고 유용하게 쓰이는 리액트 기본 제공 함수입니다.
                          • useEffect, 그렇다면 왜 중요할까?
                            1. 화면이 그려진다
                            2. useEffect가 데이터를 준비한다
                            3. 상태 데이터가 업데이트 되었으니 화면이 다시 그려진다
                            화면이 그려진 다음, 서버에게 필요한 데이터를 요청하여 받은 후, 화면을 다시그릴때 주로 사용되는데요! 아주 일반적인 패턴이므로 여러분들도 금방 이해하고 익숙해지게 됩니다!
                          • </aside>
                          • <aside> 👉 보통 useEffect는 데이터를 준비할 때 사용합니다 데이터를 준비한다는 것은, 데이터를 서버로부터 혹은 어디선가로부터 받은 후 상태(state)에 반영한다는 것을 뜻합니다. 이런순서로 말이죠
                          • [실습✍️ ] data.json을 상태(state)에 넣어서 관리해보기복습하실때 주석도 자세히 살펴보세요!
                            • [코드스니펫] MainPage.js
                            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //useState 사용법 //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수 //setState는 state를 변경시킬때 사용해야하는 함수 //모두 다 useState가 선물해줌 //useState()안에 전달되는 값은 state 초기값 const [state,setState] = useState([]) //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수 //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음 useEffect(()=>{ setState(data) },[]) // let tip = data.tip; //data.json 데이터는 state에 담기므로 상태에서 꺼내옴 let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                            <aside> 👉 그러나 실행을 하면 다음과 같은 오류 화면이 나옵니다
                            • 오류화면
                            <aside> 👉 그 이유는 이런 절차로 발생됩니다.
                            1. 화면이 그려진다
                            2. useEffect가 데이터를 state에 useState를 이용하여 업데이트한다
                            3. 상태(state)가 변경되었으니 화면이 다시 그려진다
                            여기서 1번에서 오류가 난겁니다. 화면이 가장 먼저 그려질때, 우리가 Card에 데이터를 넘기기위해 어떤일을 했나요?이런 오류는 우리가 지금 배운 개념들, 상태, 컴포넌트, useEffect를 이용하여 로딩화면을 만들어줌으로써 해결이 가능합니다!
                          • </aside>
                          • 처음부터 데이터가 없어서 , 꺼내올수 없는 오류
                          • <aside> 👉 상태(state)에서 tip 키 값에 접근하여 데이터를 꺼내 map 즉, 반복문을 돌리려했습니다. 그런데 state에 데이터가 처음부터 있었나요? 없었죠? useEffect를 거쳐야 state에 데이터가 담기는데 아직 없는 상태라 오류가 나는 겁니다.
                          • </aside>
                          • </aside>
                          • </aside>
                          • <aside> 👉 지금 꿀팁을 data.json 파일에서 꺼내 바로 JSX 문법에서 사용하고 있습니다. 이를 다음과 같이 상태에 저장한 다음 사용하도록 변경해보도록 하겠습니다.
                          04. [앱 필수 기초지식 응용] 로딩화면, 카테고리 기능, 상태 바
                          • 앱 운영에 필요한 로딩화면이때 우린 로딩화면을 만들어줌으로써 쉽게 해결이 가능합니다. 컴포넌트 폴더에 Loading.js 파일을 만들어주고 다음 코드를 넣어주세요
                            • [코드스니펫] Loading.js
                            • import React from 'react'; import {View,Text,StyleSheet} from 'react-native'; export default function Loading(){ return(<View style={styles.container}><Text style={styles.title}>준비중입니다...</Text></View>) } const styles = StyleSheet.create({ container: { //앱의 배경 색 flex:1, justifyContent:'center', alignItems:'center', backgroundColor: '#fdc453', }, title: { fontSize:20, fontWeight:'700' } })
                            <aside> 👉 그리고 MainPage.js 다음 코드 스니펫을 넣어주세요
                            • [코드스니펫] MainPage.js
                            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 const [state,setState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. **const [ready,setReady] = useState(true)** useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 **setTimeout(()=>{ setState(data) setReady(false) },1000)** },[]) // let tip = data.tip; let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" **//처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐)** return **ready ? <Loading/>** : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                            • 적용 모습
                            <aside> 👉 ready 라는 새로운 상태값이 추가되었습니다. 즉, 상태값은 컴포넌트안에 여러개가 될 수 있습니다.즉, 이런 순서로 진행된거에요!
                            1. ready 값이 true이므로 return 구문에서 ? 물음표 바로 뒤의 Loading 컴포넌트가 화면에 그려짐
                            2. 화면이 그려지고 난다음, 1초 이따가 상태값들이 채워지고 변경됨
                            3. ready 상태 값이 false가 됨
                            4. 상태값이 변경되었으므로 화면이 다시 그려짐
                            5. ready 값이 false 이므로 return 구문에서 : 콜론 뒤의 MainPage 컴포넌트가 화면에 그려짐
                            차근차근 순서따라 반복해서 보면 별거아닙니다. 정해진 규칙에 따라 우린 코드를 적용했고 정해진 규칙에 따라 코드가 실행되고 있는 거에요!
                          • </aside>
                          • useEffect안에 setTimeout이라는 함수가 존재합니다. , 뒤에 있는 숫자 (1000 === 1초) 만큼, 지연됐다가 안에 있는 코드가 실행되는 지연 함수입니다.
                          • </aside>
                          • </aside>
                          • <aside> 👉 5장에서 오류가 났던 것 처럼 화면이 그려질때 준비된 데이터가 없다!? 이러한 상황은 아주 빈번합니다.
                          • 카테고리 버튼상태값이 바뀌면 화면이 다시 그려진다! 라는 개념을 염두하고 한번 만들어보겠습니다!
                          • </aside>
                          • <aside> 👉 우리에게 이런 버튼이 있었습니다. 카테고리 버튼인데요! 이 버튼은 장식이아니라, 실제 누르면 하단의 데이터가 카테고리에 맞게 다시 정렬되는 기능이 의도되어 있습니다.
                          • [실습✍️ ] 카테고리 기능 넣기그리곤 함수가 필요합니다. 우리가 만든, 생활 재테크 반려견 버튼에 연결할 함수인데요!? 이 함수는 카테고리에 따라 카테고리 상태데이터를 새롭게 구성해주는 기능을 합니다.
                            • [코드스니펫] MainPage.js
                            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 **const [state,setState] = useState([])** //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 **const [cateState,setCateState] = useState([])** //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 **let tip = data.tip; setState(tip) setCateState(tip) setReady(false)** },1000) },[]) **const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } }** let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> **<TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>** <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, **middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 },** 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" }, **middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" },** cardContainer: { marginTop:10, marginLeft:10 }, });
                            • 제태크를 눌렀을때의 모습
                          • </aside>
                          • <aside> 👉 카테고리 기능을 위해선 먼저 카테고리 상태가 필요합니다. 카테고리에 맞는 꿀팁들을 지속적으로 저장하고 관리할 상태 그릇이죠!
                          • Expo에서 제공해주는 앱다운 앱 기능들</aside>
                          • <aside> 💡 Expo SDK. 일명 Expo에서 제공해주는 앱 기능 도구들을 개발할 때 여기서 확인 할 수 있습니다. (링크) 필요한 기능들이 있는지 목록을 쭉 보고, 원하는 기능을 선택해서 적용하면 끝!
                          • 상태 바란?</aside><aside> 💡 배터리가 충분한지, 몇시인지 매일 보시죠? 이부분도 우리가 처리를 할 수 있습니다.
                          • </aside>
                          • <aside> 💡 앱이 앱에 따라 모바일 맨 위 상태 바가 변하는 앱이 있습니다. 상태바란 이런 겁니다. 여러분들 매일 같이 보는 부분이에요!
                          • StatusBar
                            • 본격적으로 라이브러리 설치 시작!</aside><aside> 👉 터미널 우측 버튼중에 분할이란 버튼이 있습니다.</aside>
                            • 여러분들 터미널 왼편은 서버를 켜고 끄고를 담당하고(expo start) 터미널 우측은 필요한 라이브러리들을 설치할 때 명령어를 치는 장소로 분할해서 사용하면 편리합니다!
                            • <aside> 👉 이제 본격적으로 라이브러리들을 설치합니다. vscode에서 터미널을 이렇게 변경해주세요
                            • [코드스니펫] Expo 상태 바 설치
                            • expo install expo-status-bar
                            • [실습 ✍️] 적용적용 모습을 살펴볼까요?
                              • [코드스니펫] MainPage에 StatusBar 설치
                              • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; **import { StatusBar } from 'expo-status-bar';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> **<StatusBar style="black" />** <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                              <aside> 💡 style이 light일 때와, black일 때가 다릅니다. 우린 앱 화면 배경색을 검은색으로 설정했기 떄문에, 상태바를 black으로 하면 보이지 않습니다.Status Bar 공식문서
                              • black 일때
                              • light 일때
                            • </aside>
                            • 상태 바 속성은 공식문서에 다양하게 존재하니, 살펴보면서 앱에 적합한 상태 바를 적용해보세요!
                            • </aside>
                            • <aside> 💡 컴포넌트마다 다르게 적용할 수도 있고, 앱 전체에 공통적으로 적용할 수 있습니다. StatusBar를 컴포넌트 각각 다르게 둘 수 있습니다 우린 MainPage.js에 달아볼거에요!
                          05**. [앱** 페이지 적용**] 네비게이터 사용하기**
                          • 네비게이션이란?<aside> 👉 앱에 페이지 개념을 입혀주고! 웹 사이트를 이용하듯, 앱에서 여러분들이 만든 컴포넌트들을 페이지화 시켜주고, 해당 페이지끼리 이동을 가능하게 해주는 라이브러리입니다가령 다음 화면과 같이, 앱 하단에 탭 버튼을 두고 해당 버튼을 누르면 연결되어 있는 페이지로 바로바로 이동할 수 있게 해주는 기능(열 앱에서 많이 보셨죠?)도 가지고 있습니다.</aside>
                          • 따라서 이번시간에 같이 공부한 다음 틈틈히 다른 기능들도 적용해보세요!
                          • 이번 강의에선 앱 내의 페이지 구성 및 이동까지 직접 적용해볼 텐데요!, 이 라이브러리는 다양한 기능들을 가지고 있습니다.
                          • <aside> 💡 결국 이 라이브러리도 Expo에서 지원하고 있는 도구로써, 앱을 만들기 위해 사용 중인 Expo와 궁합이 아주 잘 맞습니다.
                          • </aside>
                          • <aside> 💡 물론 이것 또한 쉽게 페이징을 도와주는 외부 라이브러리를 가져다가 사용합니다. react-navigation 공식문서 보러가기 —> (링크)
                          • </aside>
                          • 현재 갖추고 있는 페이지 구성 확인!</aside>
                          • <aside> 💡 페이지 구성은 보통 pages 폴더 안에 생성해둔 컴포넌트로 구성합니다. 우리는 이미, MainPage.js 메인 페이지 DetailPage.js 상세 화면 페이지 그리고 숙제로 만든 소개화면인 AboutPage.js 까지 총 3 페이지를 가지고 있습니다.
                          • 기본 설치 코드우리에게 필요한 것이라고 했지만, 거의 대부분의 앱에 적용이 되어 있는 것들만 가져왔습니다.아래 명령어를 차례차례 터미널에 넣어 실행해주세요 가장 마지막줄은 꽤 깁니다. 이렇게 여러 라이브러리들을 띄어쓰기로 연결해서 한번에 설치 할 수도 있습니다.
                            • [코드스니펫] 네비게이션 설치 코드
                            • yarn add @react-navigation/native
                            • [코드스니펫] 네비게이션 추가 설치코드
                            • expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
                          • </aside>
                          • 아래 명령어들은 네비게이션을 사용하기 위해 필요한 기본 라이브러리들이구요! 우리가 곧 배울 스택 네비게이션와 탭 네비게이션 기능은 추가적으로 라이브러리를 또 설치 해줘야 합니다 😂
                          • <aside> 💡 리액트 네이티브에서 페이지 네비게이션을 구현하기란 조금 까다롭습니다. 그래서 딱 우리에게 필요한 것만 가져와 설치 하고, 적용해 나갈 계획입니다.
                          • 스택 네비게이션이란?</aside><aside> 💡 컴포넌트를 페이지화 시키는 스택 네비게이션은 다음과 같습니다.이렇게 만든 여러 페이지들을 책갈피 기능을 하는 스택 네비게이터에 모조리 등록시켜서, 언제든지 이 페이지 이동이 가능하게끔 해줍니다</aside>
                          • 페이지는 Stack.Screen 이라 부르며 책갈피는 Stack.Navigator라 부릅니다
                          • 우리가 페이지처럼 만든 컴포넌트를, 정말 페이지처럼 사용할 수 있게끔 페이지로 컴포넌트를 감싸 페이지로 만들어줍니다.
                          • <aside> 💡 스택 네비게이션은 컴포넌트에 페이지 기능을 부여해주고 컴포넌트에서 컴포넌트로 이동, 즉 페이지 이동을 가능하게 해줍니다
                          • createStackNavigator 사용해보기
                            • [코드스니펫] 스택 네비게이터 설치 코드
                            • yarn add @react-navigation/stack
                            • [실습 ✍️] 적용하기</aside>
                              • [코드스니펫] StackNavigator.js 스택 네비게이터
                              • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
                              <aside> 👉 워워 진정하세요! 위에서부터 차근차근 살펴볼거에요 🙂 언제나 우린 미니카 조립 설명서를 보면서 조립하듯, 공식 문서 내용 그대로 적용한다는 것 꼭 잊지마시고! 살펴보도록 하겠습니다.
                              • 💻스택 네비게이터 코드 분석
                                • 공식 문서 링크
                                • 적용 순서 1) 사용 준비
                                • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator();
                                • 적용 순서 2) 기본 틀
                                • //리액트의 모~든 파일은 컴포넌트라 생각하고 //페이지 기능을 해주는 모든 기능이 담겨 있는 컴포넌트를 만든다 생각하세요! const StackNavigator = () =>{ return ( /// 페이지 기능이 들어갈 곳 ) } export default StackNavigator;
                                • 적용 순서 3) 스크린 옵션
                                • //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
                                • 적용 순서 4) 페이지 연결
                                • {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/>
                              <aside> 👉 컴포넌트를 페이지화 했고, 페이지를 이동할 수 있는 네이게이션도 준비가 됐다면, 우리는 최상단 컴포넌트 즉 App.js에 네비게이션 기능을 달아야 합니다.</aside>
                              • [코드스니펫] App.js
                              • import React from 'react'; //이제 모든 페이지 컴포넌트들이 끼워져있는 책갈피를 메인에 둘예정이므로 //컴포넌트를 더이상 불러오지 않아도 됩니다. // import MainPage from './pages/MainPage'; // import DetailPage from './pages/DetailPage'; import { StatusBar } from 'expo-status-bar'; //메인에 세팅할 네비게이션 도구들을 가져옵니다. import {NavigationContainer} from '@react-navigation/native'; import StackNavigator from './navigation/StackNavigator' export default function App() { console.disableYellowBox = true; return ( <NavigationContainer> <StatusBar style="black" /> <StackNavigator/> </NavigationContainer>); }
                              • 스택 네비게이터 적용 후 MainPage 모습
                            • 즉, 앱 가장 최상위 코드에 네비게이션을 다는겁니다 그래야 앱 어디서든 원하는 페이지 이동이 가능할테니까요!
                            • </aside>
                            • <aside> 👉 navigation 폴더 하나를 만들고 StackNavigator.js 파일을 만들어주세요 그리고 StackNavigator 안에 다음 코드를 넣어주세요
                          • 페이지 헤더 수정</aside><aside> 💡 그 이유는 StackNavigator에서 현재 헤더에 대한 스타일과 페이지에 헤더의 제목까지 결정해주고 있기 때문입니다.
                            • 스택 네비게이터의 헤더 스타일 부분 코드
                            • <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTintColor: "#fff", headerBackTitleVisible: false }} > {/* name에 해당 하는 부분이 페이지의 타이틀이 됩니다.*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
                            <aside> 💡 따라서 헤더 스타일을 적절히 바꾸기 위해 다음과 같이 옵션 부분의 코드를 수정해보도록 하겠습니다.
                            • [코드스니펫] StackNavigator.js
                            • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
                            • [코드스니펫] MainPage.js 에서 title 삭제!
                            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                            • StackNavigator 옵션 코드 수정 후 모습
                            <aside> 💡 그래도 위에 페이지 제목으로 MainPage가 되는 모습은 뭔가 또 수정이 필요해보입니다. 이를 위해선 다음 스택 네비게이션 페이지 이동 강의에서 살펴보도록 하겠습니다
                          • </aside>
                          • </aside>
                          • </aside>
                          • <aside> 💡 그런데 현재 MainPage 화면은 다음과 같이 상단의 모습이 약간 이상해 보입니다.
                          • 페이지 이동하기일단 페이지를 이동시키려면, 책갈피가 페이지들에게 부여해준 페이지 이동 기능을 사용해야 합니다.</aside><aside> 💡 일단 먼저 구조를 보여드렸습니다. 물론 사용을 해봐야 감이 오겠죠?같이 해보죠!
                            • [실습 ✍️] 데이터 없이 페이지 이동하기
                              • [코드스니펫] MainPage.js
                              • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (**<Card content={content} key={i} navigation={navigation}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                              • [코드스니펫] Card.js
                              • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 **export default function Card({content,navigation}){** return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage')}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                            • navigation.navigate("DetailPage")
                            • [실습 ✍️] 데이터 가지고 페이지 이동하기</aside><aside> 💡 그럼 Card에서 DetailPage로 이동할 때, MainPage로 부터 넘겨받은 content도 넘겨볼까요?</aside>
                              • [코드스니펫] Card.js
                              • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function Card({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage',**content**)}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                              • [코드스니펫] DetailPage.js
                              • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage({navigation,route}) { //초기 컴포넌트의 상태값을 설정 //state, setState 뿐 아니라 이름을 마음대로 지정할 수 있음! const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) //Card.js에서 navigation.navigate 함수를 쓸때 두번째 인자로 content를 넘겨줬죠? //content는 딕셔너리 그 자체였으므로 route.params에 고대~로 남겨옵니다. //즉, route.params 는 content죠! navigation.setOptions({ //setOptions로 페이지 타이틀도 지정 가능하고 title:route.params.title, //StackNavigator에서 작성했던 옵션을 다시 수정할 수도 있습니다. headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                              • 이동한 디테일 페이지 모습
                              <aside> 💡 건네 받은 값을 꺼낼 땐, 다음과 같이 책갈피가 navigation과 추가적으로 건네준 route에서 꺼내 확인 할 수 있습니다.</aside>
                              • route에 담겨져 오는 데이터 콘솔에서 직접 확인
                              • DetailPage에서 상태값을 초기에 설정한 이유</aside><aside> 💡 그 이유는 컴포넌트가 화면에 그려지는 순서에 있습니다. 이건 지금 단계에선 조금 과한감이 있어서 간략히만 설명해보면 다음과 같습니다.
                                1. DetailPage 컴포넌트가 useState에 들어 있는 
                                   데이터 가지고 화면에 그려짐(return 함수실행)
                                2. 화면에 다 그려진후, useEffect 함수 실행
                                3. useEffect에서 상태값 변경 이벤트가 실행되면 변경된 데이터 가지고 다시 return 실행
                                4. 변경된 데이터를 가지고 화면에 DetailPage가 다시 그려짐.
                                
                                <aside> 💡 곰곰히 생각해보면 결국, 리액트 네이티브에서 화면이 변경되는 시점은 컴포넌트의 상태값이 변경될 때니까요!또는! 우리가 한번 배웠떤 Loading.js를 이용하여 데이터가 준비가 되면 로딩 화면을 치우고, 본 화면을 보여준다던가!</aside>
                              • 여러분들 마음껏 화면 처리를 해주시면 됩니다!
                              • 이 개념은 어려울 수 있습니다 충분히! 따라서 처음에는, 어떠한 데이터를 보여주는 컴포넌트라면, 무조건 초기값을 의미없는 값이더라도 넣고 시작한다! 라고 생각하시면 편합니다!
                              • </aside>
                              • //초기 컴포넌트의 상태값을 설정 const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" })
                              • <aside> 💡 DetailPage 초반에 우린 이렇게 상태값을 설정해놨었습니다. 그 이유가 뭘까요? 심지어 이 상태값을 초기에 설정안하면 오류가 발생합니다. tip엔 아무것도 없다며...
                            • route.params 객체에 건네준 딕셔너리가 넘겨 있습니다!
                            • 왜냐하면 지금은 어떠한 카드를 눌러도 상세 페이지에서 동일한 데이터를 보게 되잖아요? tip 데이터를 고정해놨으니까요!
                            • navigation.navigate("Detail",**{** title**:** title **})**
                            • <aside> 💡 버튼 카드에서 사용한 함수에 아래와 같이 두 번째 인자로 딕셔너리를 넘겨주면, 우리는 이동 한 페이지에서 넘겨준 데이터를 받을 수 있습니다.
                          • </aside>
                          • 그럼 이제 Card.js에 페이지 이동 기능을 달아보겠습니다. 코드를 실행 한 다음 페이지를 이동해 보면 익숙한 뒤로 가기 버튼도 보일거에요!
                          • //navigation 객체가 가지고 있는 두 함수(setOptions와 navigate) //해당 페이지의 제목을 설정할 수 있음 navigation.setOptions({ title:'나만의 꿀팁' }) //Stack.screen에서 name 속성으로 정해준 이름을 지정해주면 해당 페이지로 이동하는 함수 navigation.navigate("DetailPage") //name 속성을 전달해주고, 두 번째 인자로 딕셔너리 데이터를 전달해주면, Detail 페이지에서 //두번째 인자로 전달된 딕셔너리 데이터를 route 딕셔너리로 로 받을 수 있음 navigation.navigate("DetailPage",**{ title: title }**) //전달받은 데이터를 받는 route 딕셔너리 //비구조 할당 방식으로 route에 params 객체 키로 연결되어 전달되는 데이터를 꺼내 사용 //navigate 함수로 전달되는 딕셔너리 데이터는 다음과 같은 모습이기 때문입니다. /* { route : { params :{ **title:title** } } } */ **const {** title**} = route.params;**
                          • Stack.screen에 등록된 모든 페이지 컴포넌트들은 navigation 와 route 라는 딕셔너리(객체)를 속성으로 넘겨받아 사용할 수 있습니다. 이 두 딕셔너리는 다음과 같은 기능을 갖습니다.
                          • <aside> 💡 Main 페이지(MainPage.js)에서 카드 버튼을 누르면(Card.js) 꿀팁 상세 페이지(DetailPage.js)로 이동합니다
                          06**. [Expo 앱다운 앱기능] 공유 기능과 외부 링크 추가**
                          • Share그런데 결과를 친구한테 공유하고 싶은 마음이 들수도 있겠네요 결과를 공유해주고, 앱다운로드 주소까지 전달해준다면 여러분이 만든 앱 홍보하기가 수월하겠죠?
                          • </aside>
                          • <aside> 👉 디테일 페이지까지 이동해서 도달했다면 여러분은 앱 개발자!
                          • Share 적용해보기
                            • 준비!
                              • 설치 할 라이브러리 없이, react-native에서 기본적으로 제공해주는 공유 기능을 사용합니다.
                              import { Share } from "react-native";
                              
                              • 그리고 우린 다음과 같은 공유 버튼을 디테일 페이지 하단에 생성하고 기능을 만들꺼에요! 한번 잠깐 영상을 멈추고 기능은 생각 말고 다음 버튼을 추가한 화면만 그려보세요! 복습 복습
                              • 버튼 추가 디테일 페이지 모습
                            • [실습 ✍️] 적용</aside>
                              • [코드스니펫] DetailPage.js
                              • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { **Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); }** return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}**>팁 공유하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:100, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                              • 공유하는 모습
                            • <aside> 💡 share 함수를 만들고 거기에 react-native가 기본적으로 제공해주는 Share 함수를 사용하여 간단히 공유를 할 수 있습니다
                            • 공유 모습
                          • Linking이번엔 앱에서 외부 링크를 여는 방법에 대해 배워봅니다. 버튼을 누르면, 버튼에 연결 시킨 기능을 통해 외부 링크를 핸드폰에 있는 기본 브라우저로 열어봅니다.
                          • </aside>
                          • <aside> 👉 디테일 페이지에 있는 내용들이 사실 어디에서 가져온거라면? 출처를 남겨야 할겁니다. 그리고 그 출처로 바로가기 버튼정도도 있으면 여러분들이 만드는 다양한 플랫폼들을 서로 연결 시킬 수 있겠죠?
                          • Linking 적용해보기
                            • 준비!
                              • DetailPage에 버튼을 다음과 같이 추가해주세요
                              • 링크 버튼 추가 된 DetailPage 모습
                              • expo 에서 제공해주는 도구를 설치 한다음, 해당 도구를 상단에 가져와 준비해야 합니다
                              expo install expo-linking
                              
                              import * as Linking from 'expo-linking';
                              
                            • [실습 ✍️] 적용
                              • [코드스니펫] DetailPage.js
                              • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; import * as Linking from 'expo-linking'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); } const link = () => { Linking.openURL("<https://spartacodingclub.kr>") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}>팁 공유하기 link()}>외부 링크 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:90, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                          07**. 3주차 끝 & 숙제 설명**
                            1. 어바웃 화면 페이지화 시키고 버튼 추가하기!
                            <aside> 👉 지난번 숙제로 만든 AboutPage.js를 아직 우린 스택 네비게이터에 연결하지 않았습니다.추가적으로 외부 링크 걸기까지 배웠으니, 여러분 인스타나 블로그 주소 혹은 아무주소를 AboutPage 하단 버튼에 연결해주세요!
                            • 만들 화면
                            • 가이드
                              1. 메인에 TouchableOpacity 버튼을 두고 navigate를 연결시키면 이동이 가능하겠죠?
                              2. Stack.screen에 AboutPage.js를 연결시켜야합니다!
                              3. 소개 페이지 상태바의 디테일함을 주목하세요! 흰색이네요 🙂
                          • </aside>
                          • 따라서 여러분이 직접! 스택네비게이터에 AboutPage.js를 달아보구요! 하단 메인페이지의 상단에 다음과 같은 버튼을 만들어서 누르면 AboutPage로 가게끔 해주세요!
                            1. 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요
                          • 가이드
                              1. 찜 데이터 제공
                              <aside> 💡 나만의 꿀 팁 앱에서 찜 페이지란 쉽게 말해 팁 리스트 중에 선택한 팁들을 모아보는 페이지입니다. 이런 찜 페이지 자주 보셨죠?
                              • 즉, 다음 상태값으로 화면을 구성해주세요::: 리스트에 딕셔너리 두 개가 들어있는 데이터입니다.
                              </aside>
                              • [코드스니펫] 찜 상태 데이터
                              • const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }])
                            • 하지만 아직 데이터를 저장하고, 수정하고 등등의 데이터 관리 부분을 배우지 않았기 때문에 [코드스니펫] 찜 상태 데이터를 가지고 화면만 그려볼겁니다.
                              1. 파일의 위치와 이름
                              <aside> 💡 새로운 페이지를 만드는 중이니, 어디에 위치해야 할까요? 당연히 pages 폴더에 위치시켜야 겠죠?</aside>
                            • 또한 파일이름은 우리 LikePage.js 로 만들어 봅시다! ZzimPage는 좀...
                              1. 메인의 꿀팁찜 버튼을 누르면 찜 페이지로!
                              <aside> 💡 혼자만의 큰그림이 있었습니다. 메인페이지에 꿀팁찜 버튼이란걸 강의 초반부터 만들어 놓았었어요! 이 버튼을 누르면 찜 페이지로 가게끔 해봅시다!
                            • </aside>
                              1. Card.js 를 복붙해서 찜 페이지에서만 사용하는 LikeCard.js 만들기
                              <aside> 💡 찜 목록도 Card.js와 크게 다를건 없습니다. 기존 카드에 찜 삭제 버튼정도가 추가 되었다는 점?이 다르므로, Card.js 코드를 복붙해서 LikeCard.js를 만들어 봅시다</aside>
                            • 그런데! LikeCard.js에선 TouchableOpacity로 감싸는게 아닌! View 태그로 카드 전체를 감싸주도록 바꿔주세요!
                              1. 최종 폴더 & 파일 모습
                          HW. 3주차 숙제 해설
                          • 숙제 1: 어바웃 화면 페이지화 시키고 버튼 추가하기!
                            • [코드스니펫] StackNavigator.js(숙제1)
                            • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> </Stack.Navigator> ) } export default StackNavigator;
                            • [코드스니펫] AboutPage.js(숙제1)
                            • import React,{useEffect} from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' import { StatusBar } from 'expo-status-bar'; export default function AboutPage({navigation,route}){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" useEffect(()=>{ navigation.setOptions({ title:"소개 페이지", headerStyle: { backgroundColor: '#1F266A', shadowColor: "#1F266A", }, headerTintColor: "#fff", }) },[]) return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:20, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
                            • [코드스니펫] MainPage.js(숙제1)
                            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20, }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
                          • 숙제 2: 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요!
                            • [코드스니펫] StackNavigator.js(숙제2)
                            • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; import LikePage from '../pages/LikePage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> <Stack.Screen name="LikePage" component={LikePage}/> </Stack.Navigator> ) } export default StackNavigator;
                            • [코드스니펫] LikeCard.js(숙제2)
                            • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function LikeCard({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <View style={styles.card}> <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> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                            • [코드스니펫] MainPage.js(숙제2)
                            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> **<TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>** </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
                            • [코드스니펫] LikePage.js(숙제2)
                            • import React,{useState, useEffect} from 'react'; import {ScrollView, Text, StyleSheet} from 'react-native'; import LikeCard from '../components/LikeCard'; import Card from '../components/Card'; export default function LikePage({navigation,route}){ const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }]) useEffect(()=>{ navigation.setOptions({ title:'꿀팁 찜' }) }) return ( { tip.map((content,i)=>{ return() }) } ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#fff" } })01. 오늘 배울 것
                              • 오늘 배울 내용이 두 가지는 앞으로도 계속~ 반복 숙달합니다 🙂
                                1. 리액트 필수 지식 활용
                                2. Expo 기능 사용
                                3. 페이지 적용
                                </aside>
                              • 오늘은 앱다운 앱을 만드는 연습과 기술을 배울텐데요! 다음 순서대로 지금까지 배웠던 것을 복습 하면서 앱다운 앱 기능을 배우니 점점 더 여러분은 앱 개발에 숙달되는 모습을 볼 수 있습니다!
                              • <aside> 👉 지난 시간까지 자바스크립트 기초체력을 다지고 JSX로 화면을 그리는 방법 까지 배워봤습니다.
                              • [앱 필수 기초지식] 리액트 필수지식
                                • 컴포넌트(Component)
                                • 상태(State,useState)
                                • 속성(Props)
                                • useEffect
                                이렇게 네 가지입니다. 단어가 조금 어려워 보이지만, 재미있고 간단한 개념들입니다. 이 개념들을 통해 앱 코드를 체계적으로 관리할 수 있고 화면안에서 데이터를 자유자재로 관리할 수도 있습니다.리액트 네이티브(React Native)는 리액트(React.js) 기반으로 만들어진 앱 개발 기술이기 때문이에요! 겁먹지마시고 차근차근 다루어봅시다.
                              • </aside>
                              • 최소한의 리액트 개념을 배우는 이유는
                              • <aside> 👉 리액트 네이티브 앱을 만들기 위해서 알아야 하는 최소한의 리액트 개념을 배웁니다.
                              • [Expo 앱다운 앱기능] 앱다운 앱을 위해 사용할 리액트 네이티브와 Expo 기능들이렇게 앱 다운 앱이 되기위한 기능들을 공식문서를 보고 하나하나 적용해보는 시간을 갖습니다. 이 연습을 거치면 여러분들은 공식문서를 보며 필요한 앱 기능들을 여러분들 앱에 붙여나갈 수 있습니다!
                              • </aside>
                              • <aside> 👉 앱이라고 하면 보통 현재 보고 있는 내용을 친구들한테 공유하거나, 앱 안에 있는 링크를 누르면 바로 해당 링크로 화면이 전환되는 기능들이 들어 있습니다. 또한 배터리를 볼 수 있는 상태바 스타일도 앱 전체적인 스타일에 맞게 변경할 수도 있어야 할겁니다.
                              • [앱 페이지 적용] 앱에 페이지 기능 넣기!그래서 앱안에 여러 페이지를 두고 버튼을 눌러 이동할 수 있는 기능을 달아봅니다. 본격적으로 거의 온전한 앱의 모습을 띄게 됩니다!
                              • </aside>
                              • <aside> 👉 앱에 페이지가 하나라면 너무 단조롭고 볼게 없는 앱이 되겠죠?
                              02. [함께해보기] 나만의 꿀팁 앱 상세 화면
                              • [실습 ✍️ ] 나만의 꿀팁 상세 페이지가될 아래의 화면을 만들어보세요
                              • 퀴즈 가이드
                                • [현재 코드 상황]
                                  • App.js
                                  • import React from 'react' import MainPage from './pages/MainPage'; import AboutPage from './pages/AboutPage'; export default function App(){ // return (<MainPage/>) return (<AboutPage/>) }
                                  • MainPage.js
                                  • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { 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> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, 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", } });
                                  • AboutPage.js
                                  • import React from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' export default function AboutPage(){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:100, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
                                  • 폴더구조
                                • DetailPage.js를 pages 폴더에 만들어 넣기</aside>
                                • <aside> 👉 저번 1주차 마무리 시간에, pages 폴더를 만들고 AboutPage.js 파일을 만들어 넣었던 기억이 나시나요? 그때랑 동일하게 pages 폴더안에 DetailPage.js 파일을 만들고 App.js에서 return 부분에 DetailPage.js를 넣어주세요 다음과 같이요!
                                • 문제 디테일 화면을 위한 데이터딕셔너리 형태로 키값에 접근해서 값을 꺼내 사용해도 되고! 문자열들 그대로 복사해서 Text 태그에 넣어 사용해도 됩니다!
                                  • [코드스니펫] 상세 화면에서 사용될 데이터
                                  • const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }
                                • </aside>
                                • <aside> 👉 화면에 나타낼 꿀 팁은 다음 데이터를 사용하세요.
                              • 함께 해보기</aside>
                                • [코드스니펫] DetailPage.js 화면
                                • import React from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage() { const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" } const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                              • <aside> 👉 DetailPage를 확인하려면 App.js를 좀 바꿔야 겠죠? 어떻게 바꿔야 할까요?
                              03**.** [앱 필수 기초지식] 컴포넌트, 속성, 상태, useEffect
                              • 공부 할 리액트 필수 지식<aside> 👉 다시 떠올리자면, 리액트 네이티브는 리액트(React.js) 라이브러리 기반으로 만들어진 프레임워크 입니다. 그렇기 때문에 기본적인 구조는 리액트를 닯아 있습니다.1) 컴포넌트(Component) : 정해진 엘리먼트들(요소)을 사용하여 만든 화면의 일부분 2) 상태(State) : 컴포넌트에서 데이터를 유지하고 관리하기 위한 유일한 방법 == 그냥 사용할 데이터! 3) 속성(Props) : 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하는 방식 == 그냥 데이터 전달! 4) useEffect : 화면에 컴포넌트가 그려지면 처음 실행해야 하는 함수들을 모아두는 곳</aside>
                              • 네 가지가 있습니다. 워워 어렵지 않아요. 이것도 정해진 규칙과 개념일 뿐! 사용방법 숙지!만 하면 그만이랍니다!
                              • 대표적으로
                              • 컴포넌트(Component)</aside><aside> 👉 컴포넌트는 즉, 화면의 모든 부분입니다. 그리고 컴포넌트란 App.js의 큰 App 함수처럼, 코드 전체를 감싸고 있는 함수를 뜻하기도 합니다.</aside>이렇게 컴포넌트로 생각하고 앱을 개발해 나아간다면, 코드 재사용이 용이 해집니다.실제 우리 예제에 어떻게 적용할 수 있는지. 확인해보겠습니다.
                              • </aside>
                              • 코드 재사용이란 어려운 용어는 아니고, 버튼 하나를 만들었을 때 이 버튼 코드를 여러 페이지에서 사용 가능하다는 뜻입니다.
                              • <aside> 👉 버튼 하나가 컴포넌트가 될 수 있고, 버튼을 모아둔 영역이 컴포넌트가 될 수 있습니다.
                              • 따라서 App.js를 App 컴포넌트라고도 부를 수 있습니다.
                              • <aside> 👉 컴포넌트는 UI의 요소, 요소를 재사용 가능한 부분으로 조각내서 운영하는 기법입니다. 리액트 기반으로 만들어진 페이스북 웹사이트는 운영되는 컴포넌트가 수 만가지라고 합니다
                              • [실습 ✍️ ] 메인화면 컴포넌트화 해보기그럼 다음 코드를 Card.js에 넣어주세요! 어디서 많이 본 코드입니다 그쵸?<aside> 🚧 마찬가지로 MainPage.js 화면을 확인하려면 App.js를 바꿔야 겠죠? return (<MainPage/>) 으로요!
                                • [코드스니펫] Card.js
                                • import React from "react" import {View,Text,Image,StyleSheet} from "react-native"; //비구조 할당 방식으로 넘긴 속성 데이터를 꺼내 사용함 export default function Card({content}) { return (<View style={styles.card}> <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>) } const styles = StyleSheet.create({ 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", } })
                                <aside> 👉 맞습니다! MainPage.js에서 이미지위에 텍스트가 있던, 한 줄에 세 개씩 나열 되었던 그 카드 버튼입니다!.</aside>
                                • [코드스니펫] MainPage.js
                                • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; **import Card from '../components/Card';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (**<Card content={content} key={i}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                <aside> 👉 MainPage.js에 있던 코드들을 Card.js로 옮겼습니다. 이렇게 옮기게 되면, 추후에 MainPage.js가 아닌 다른 페이지(페이지 또한 컴포넌트로 볼 수 있습니다) 또는 컴포넌트에서 Card.js를 사용할 수 있습니다.</aside>
                                • MainPage.js
                                • Card.js
                                <aside> 👉 미리 말씀드리자면 MainPage.js에서 Card.js로 데이터를 넘기고 있는 모습입니다! 이를 속성을 넘긴다! 라고 보통 일컫는데 이는 다음 챕터에서 더 자세히 다루도록 하겠습니다!
                              • </aside>
                              • 그런데 약간 생소한 부분이 있습니다 다음 두 부분인데요!
                              • 그럼 MainPage.js는 다음과 같이 변경해주세요
                              • </aside>
                              • </aside>
                              • <aside> 👉 Components 폴더 안에 Card.js라는 파일을 만듭니다. 보통 컴포넌트들은 components 폴더에 모아두면서 관리하곤 합니다.
                              • 속성(Props)<Text> 태그엔 numberOfLines이란 속성이 있었습니다. 말줌임표 효과를 주었죠! <Image> 태그엔 resizeMode란 속성이 있었습니다. 이미지가 영역을 차지하는 방식을 나타냈죠.</aside>예를 들어 Card를 사용하는 곳에서<Card image={'이미지 주소'}/> 라고 속성을 써 넣으면, 이 <Card/>는 image라는 속성이름에 {'이미지 주소'} 값을 을 갖게 됩니다. 즉 키와 벨류의 값을 갖게 됩니다 근데 이게 무슨 의미가 있을까요?</aside>
                              • 의미가 있습니다. 속성을 부여받은 컴포넌트에서 해당 속성 값을 받아서 사용 할 수 있거든요! 다음과 같이 말이죠!
                              • <aside> 👉 우리가 만든 <Card/> 컴포넌트에도 속성이 존재합니다. 우리가 부여 하면 부여하는 대로 속성이 되는 겁니다.
                              • 이 모든 속성들은 공식 문서에 나와 있고, 해당 엘리먼트들이 태어날 때부터 가지고 있는 속성이였습니다.
                              • <aside> 👉 속성은 쉽게 생각해서 컴포넌트에 데이터를 전달한다는 것이다. 그 전달 모습은 키와 벨류의 형태입니다.
                              • [확인해보기👀 ]</aside>
                                • MainPage.js 에서의 Card.js 컴포넌트
                                <aside> 👉 어렵지 않은 규칙이 숨어 있습니다!
                                1. 컴포넌트에 속성(데이터)을 부여해줘서 전달할땐, 키와 벨류(content={content}) 형태로 전달해줘야 할 것
                                2. 컴포넌트를 반복문 돌릴땐, 컴포넌트마다 고유하다는 것을 표현하기 위해, map에서 나오는 인덱스(i)를 key = {i} 속성 전달 형태로 꼭 넣을것! 입니다.
                                </aside>
                                • Card.js 에서의 속성 값 내려 받기
                                <aside> 👉 오랜만에 복습겸 나온 비구조 할당 방식 의 모습입니다. MainPage.js에서 넘겨준 속성은 실제 받게되는 컴포넌트에서 정말 딕셔너리 데이터를 받았다! 라고 생각하면 됩니다.이 방식으로 넘겨준 키값을 {키,키,키} 비구조 할당 방식으로 바로 꺼내서 사용하면 됩니다!
                              • </aside>
                              • 때문에, 비구조 할당 방식이 뭐였죠? 딕셔너리에서 키값을 바로 취해서 변수로써 함수안에서 즉시 사용할 수 있는 방식이였죠?
                              • <aside> 👉 MainPage.js에서 방금전 컴포넌트화! 했던 Card.js를 우린 이렇게 사용했습니다.
                              • 상태(State, useState)<aside> 👉 컴포넌트마다 데이터를 보유하고 관리 할 수 있습니다. 데이터라고 불러도 되지만, 리액트에서는 컴포넌트에서 보유/관리 되는 데이터를 **상태**라 부릅니다.사용방법은 실습을 통해 알아보도록 하겠습니다!
                              • </aside>
                              • 리액트에서 상태(state)는 리액트 라이브러리에서 제공해주는 useState로 생성하고 setState 함수로 정/변경 할 수 있습니다.
                              • useState, 그렇다면 왜 중요할까?여러분이 만드는 화면은 데이터에 따라 변경됩니다 또 아무 데이터가 아니라 이 상태(state)로 관리되는 데이터가 변경되면 화면이 바뀝니다.</aside><aside> 👉 당시 개발자들끼리 이 상태와 컴포넌트의 관계를 공식으로 표현한 글을 보고 대단하다!! 했던 기억이 있는데, 여러분들은 어떻게 느끼실지 궁금하네요 ㅎㅎ무슨말인지 아직 잘 와닿지 않으시죠? 다른건 잘 와닿지 않아도 state는 컴포넌트에서 관리되는 상태만 기억하시면 충분합니다 이 개념은 이번 강의에서 개념을 다 배운 뒤, 다음 6 강에서 실습 하며 직접 확인할 수 있습니다
                              • </aside>
                              • 저 공식은 즉, 사용자 화면(UI)는 컴포넌트(component)에 어떤 데이터(state)가 주입되고 변경되냐에 따라 변화된다를 뜻합니다
                              • UI = component(state)
                              • 모르셔도 되지만 꽤 유의미한 공식이 있습니다
                              • <aside> 👉 리액트는 특이한 점이 있습니다.
                              • 화면이 그려진다음 가장 먼저 실행되는 함수, useEffect화면이 그려진 다음 가장 먼저 실행되는 함수로 사용방법이 간단합니다 형식만 알고 있으면 충분합니다
                                **useEffect(()=>{**
                                
                                	...화면이 그려진 다음 가장 먼저 실행되야 할 코드 작성 공간
                                
                                **},[])**
                                
                                <aside> 👉 useEffect(()=>{ },[])</aside>
                              • 이 안에서, 화면이 그려진다음 실행시키고 싶은 함수를 작성한다면 가장먼저 실행이 됩니다. useEffect 또한 어떤 역할을 하는지만 알고! 바로 실습을 통해 확인해보도록 하겠습니다
                              • </aside>
                              • <aside> 👉 useEffect는 너무 간단하고 유용하게 쓰이는 리액트 기본 제공 함수입니다.
                              • useEffect, 그렇다면 왜 중요할까?
                                1. 화면이 그려진다
                                2. useEffect가 데이터를 준비한다
                                3. 상태 데이터가 업데이트 되었으니 화면이 다시 그려진다
                                화면이 그려진 다음, 서버에게 필요한 데이터를 요청하여 받은 후, 화면을 다시그릴때 주로 사용되는데요! 아주 일반적인 패턴이므로 여러분들도 금방 이해하고 익숙해지게 됩니다!
                              • </aside>
                              • <aside> 👉 보통 useEffect는 데이터를 준비할 때 사용합니다 데이터를 준비한다는 것은, 데이터를 서버로부터 혹은 어디선가로부터 받은 후 상태(state)에 반영한다는 것을 뜻합니다. 이런순서로 말이죠
                              • [실습✍️ ] data.json을 상태(state)에 넣어서 관리해보기복습하실때 주석도 자세히 살펴보세요!
                                • [코드스니펫] MainPage.js
                                • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //useState 사용법 //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수 //setState는 state를 변경시킬때 사용해야하는 함수 //모두 다 useState가 선물해줌 //useState()안에 전달되는 값은 state 초기값 const [state,setState] = useState([]) //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수 //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음 useEffect(()=>{ setState(data) },[]) // let tip = data.tip; //data.json 데이터는 state에 담기므로 상태에서 꺼내옴 let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                <aside> 👉 그러나 실행을 하면 다음과 같은 오류 화면이 나옵니다
                                • 오류화면
                                <aside> 👉 그 이유는 이런 절차로 발생됩니다.
                                1. 화면이 그려진다
                                2. useEffect가 데이터를 state에 useState를 이용하여 업데이트한다
                                3. 상태(state)가 변경되었으니 화면이 다시 그려진다
                                여기서 1번에서 오류가 난겁니다. 화면이 가장 먼저 그려질때, 우리가 Card에 데이터를 넘기기위해 어떤일을 했나요?이런 오류는 우리가 지금 배운 개념들, 상태, 컴포넌트, useEffect를 이용하여 로딩화면을 만들어줌으로써 해결이 가능합니다!
                              • </aside>
                              • 처음부터 데이터가 없어서 , 꺼내올수 없는 오류
                              • <aside> 👉 상태(state)에서 tip 키 값에 접근하여 데이터를 꺼내 map 즉, 반복문을 돌리려했습니다. 그런데 state에 데이터가 처음부터 있었나요? 없었죠? useEffect를 거쳐야 state에 데이터가 담기는데 아직 없는 상태라 오류가 나는 겁니다.
                              • </aside>
                              • </aside>
                              • </aside>
                              • <aside> 👉 지금 꿀팁을 data.json 파일에서 꺼내 바로 JSX 문법에서 사용하고 있습니다. 이를 다음과 같이 상태에 저장한 다음 사용하도록 변경해보도록 하겠습니다.
                              04. [앱 필수 기초지식 응용] 로딩화면, 카테고리 기능, 상태 바
                              • 앱 운영에 필요한 로딩화면이때 우린 로딩화면을 만들어줌으로써 쉽게 해결이 가능합니다. 컴포넌트 폴더에 Loading.js 파일을 만들어주고 다음 코드를 넣어주세요
                                • [코드스니펫] Loading.js
                                • import React from 'react'; import {View,Text,StyleSheet} from 'react-native'; export default function Loading(){ return(<View style={styles.container}><Text style={styles.title}>준비중입니다...</Text></View>) } const styles = StyleSheet.create({ container: { //앱의 배경 색 flex:1, justifyContent:'center', alignItems:'center', backgroundColor: '#fdc453', }, title: { fontSize:20, fontWeight:'700' } })
                                <aside> 👉 그리고 MainPage.js 다음 코드 스니펫을 넣어주세요
                                • [코드스니펫] MainPage.js
                                • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 const [state,setState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. **const [ready,setReady] = useState(true)** useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 **setTimeout(()=>{ setState(data) setReady(false) },1000)** },[]) // let tip = data.tip; let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" **//처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐)** return **ready ? <Loading/>** : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                • 적용 모습
                                <aside> 👉 ready 라는 새로운 상태값이 추가되었습니다. 즉, 상태값은 컴포넌트안에 여러개가 될 수 있습니다.즉, 이런 순서로 진행된거에요!
                                1. ready 값이 true이므로 return 구문에서 ? 물음표 바로 뒤의 Loading 컴포넌트가 화면에 그려짐
                                2. 화면이 그려지고 난다음, 1초 이따가 상태값들이 채워지고 변경됨
                                3. ready 상태 값이 false가 됨
                                4. 상태값이 변경되었으므로 화면이 다시 그려짐
                                5. ready 값이 false 이므로 return 구문에서 : 콜론 뒤의 MainPage 컴포넌트가 화면에 그려짐
                                차근차근 순서따라 반복해서 보면 별거아닙니다. 정해진 규칙에 따라 우린 코드를 적용했고 정해진 규칙에 따라 코드가 실행되고 있는 거에요!
                              • </aside>
                              • useEffect안에 setTimeout이라는 함수가 존재합니다. , 뒤에 있는 숫자 (1000 === 1초) 만큼, 지연됐다가 안에 있는 코드가 실행되는 지연 함수입니다.
                              • </aside>
                              • </aside>
                              • <aside> 👉 5장에서 오류가 났던 것 처럼 화면이 그려질때 준비된 데이터가 없다!? 이러한 상황은 아주 빈번합니다.
                              • 카테고리 버튼상태값이 바뀌면 화면이 다시 그려진다! 라는 개념을 염두하고 한번 만들어보겠습니다!
                              • </aside>
                              • <aside> 👉 우리에게 이런 버튼이 있었습니다. 카테고리 버튼인데요! 이 버튼은 장식이아니라, 실제 누르면 하단의 데이터가 카테고리에 맞게 다시 정렬되는 기능이 의도되어 있습니다.
                              • [실습✍️ ] 카테고리 기능 넣기그리곤 함수가 필요합니다. 우리가 만든, 생활 재테크 반려견 버튼에 연결할 함수인데요!? 이 함수는 카테고리에 따라 카테고리 상태데이터를 새롭게 구성해주는 기능을 합니다.
                                • [코드스니펫] MainPage.js
                                • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 **const [state,setState] = useState([])** //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 **const [cateState,setCateState] = useState([])** //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 **let tip = data.tip; setState(tip) setCateState(tip) setReady(false)** },1000) },[]) **const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } }** let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> **<TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>** <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, **middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 },** 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" }, **middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" },** cardContainer: { marginTop:10, marginLeft:10 }, });
                                • 제태크를 눌렀을때의 모습
                              • </aside>
                              • <aside> 👉 카테고리 기능을 위해선 먼저 카테고리 상태가 필요합니다. 카테고리에 맞는 꿀팁들을 지속적으로 저장하고 관리할 상태 그릇이죠!
                              • Expo에서 제공해주는 앱다운 앱 기능들</aside>
                              • <aside> 💡 Expo SDK. 일명 Expo에서 제공해주는 앱 기능 도구들을 개발할 때 여기서 확인 할 수 있습니다. (링크) 필요한 기능들이 있는지 목록을 쭉 보고, 원하는 기능을 선택해서 적용하면 끝!
                              • 상태 바란?</aside><aside> 💡 배터리가 충분한지, 몇시인지 매일 보시죠? 이부분도 우리가 처리를 할 수 있습니다.
                              • </aside>
                              • <aside> 💡 앱이 앱에 따라 모바일 맨 위 상태 바가 변하는 앱이 있습니다. 상태바란 이런 겁니다. 여러분들 매일 같이 보는 부분이에요!
                              • StatusBar
                                • 본격적으로 라이브러리 설치 시작!</aside><aside> 👉 터미널 우측 버튼중에 분할이란 버튼이 있습니다.</aside>
                                • 여러분들 터미널 왼편은 서버를 켜고 끄고를 담당하고(expo start) 터미널 우측은 필요한 라이브러리들을 설치할 때 명령어를 치는 장소로 분할해서 사용하면 편리합니다!
                                • <aside> 👉 이제 본격적으로 라이브러리들을 설치합니다. vscode에서 터미널을 이렇게 변경해주세요
                                • [코드스니펫] Expo 상태 바 설치
                                • expo install expo-status-bar
                                • [실습 ✍️] 적용적용 모습을 살펴볼까요?
                                  • [코드스니펫] MainPage에 StatusBar 설치
                                  • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; **import { StatusBar } from 'expo-status-bar';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> **<StatusBar style="black" />** <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                  <aside> 💡 style이 light일 때와, black일 때가 다릅니다. 우린 앱 화면 배경색을 검은색으로 설정했기 떄문에, 상태바를 black으로 하면 보이지 않습니다.Status Bar 공식문서
                                  • black 일때
                                  • light 일때
                                • </aside>
                                • 상태 바 속성은 공식문서에 다양하게 존재하니, 살펴보면서 앱에 적합한 상태 바를 적용해보세요!
                                • </aside>
                                • <aside> 💡 컴포넌트마다 다르게 적용할 수도 있고, 앱 전체에 공통적으로 적용할 수 있습니다. StatusBar를 컴포넌트 각각 다르게 둘 수 있습니다 우린 MainPage.js에 달아볼거에요!
                              05**. [앱** 페이지 적용**] 네비게이터 사용하기**
                              • 네비게이션이란?<aside> 👉 앱에 페이지 개념을 입혀주고! 웹 사이트를 이용하듯, 앱에서 여러분들이 만든 컴포넌트들을 페이지화 시켜주고, 해당 페이지끼리 이동을 가능하게 해주는 라이브러리입니다가령 다음 화면과 같이, 앱 하단에 탭 버튼을 두고 해당 버튼을 누르면 연결되어 있는 페이지로 바로바로 이동할 수 있게 해주는 기능(열 앱에서 많이 보셨죠?)도 가지고 있습니다.</aside>
                              • 따라서 이번시간에 같이 공부한 다음 틈틈히 다른 기능들도 적용해보세요!
                              • 이번 강의에선 앱 내의 페이지 구성 및 이동까지 직접 적용해볼 텐데요!, 이 라이브러리는 다양한 기능들을 가지고 있습니다.
                              • <aside> 💡 결국 이 라이브러리도 Expo에서 지원하고 있는 도구로써, 앱을 만들기 위해 사용 중인 Expo와 궁합이 아주 잘 맞습니다.
                              • </aside>
                              • <aside> 💡 물론 이것 또한 쉽게 페이징을 도와주는 외부 라이브러리를 가져다가 사용합니다. react-navigation 공식문서 보러가기 —> (링크)
                              • </aside>
                              • 현재 갖추고 있는 페이지 구성 확인!</aside>
                              • <aside> 💡 페이지 구성은 보통 pages 폴더 안에 생성해둔 컴포넌트로 구성합니다. 우리는 이미, MainPage.js 메인 페이지 DetailPage.js 상세 화면 페이지 그리고 숙제로 만든 소개화면인 AboutPage.js 까지 총 3 페이지를 가지고 있습니다.
                              • 기본 설치 코드우리에게 필요한 것이라고 했지만, 거의 대부분의 앱에 적용이 되어 있는 것들만 가져왔습니다.아래 명령어를 차례차례 터미널에 넣어 실행해주세요 가장 마지막줄은 꽤 깁니다. 이렇게 여러 라이브러리들을 띄어쓰기로 연결해서 한번에 설치 할 수도 있습니다.
                                • [코드스니펫] 네비게이션 설치 코드
                                • yarn add @react-navigation/native
                                • [코드스니펫] 네비게이션 추가 설치코드
                                • expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
                              • </aside>
                              • 아래 명령어들은 네비게이션을 사용하기 위해 필요한 기본 라이브러리들이구요! 우리가 곧 배울 스택 네비게이션와 탭 네비게이션 기능은 추가적으로 라이브러리를 또 설치 해줘야 합니다 😂
                              • <aside> 💡 리액트 네이티브에서 페이지 네비게이션을 구현하기란 조금 까다롭습니다. 그래서 딱 우리에게 필요한 것만 가져와 설치 하고, 적용해 나갈 계획입니다.
                              • 스택 네비게이션이란?</aside><aside> 💡 컴포넌트를 페이지화 시키는 스택 네비게이션은 다음과 같습니다.이렇게 만든 여러 페이지들을 책갈피 기능을 하는 스택 네비게이터에 모조리 등록시켜서, 언제든지 이 페이지 이동이 가능하게끔 해줍니다</aside>
                              • 페이지는 Stack.Screen 이라 부르며 책갈피는 Stack.Navigator라 부릅니다
                              • 우리가 페이지처럼 만든 컴포넌트를, 정말 페이지처럼 사용할 수 있게끔 페이지로 컴포넌트를 감싸 페이지로 만들어줍니다.
                              • <aside> 💡 스택 네비게이션은 컴포넌트에 페이지 기능을 부여해주고 컴포넌트에서 컴포넌트로 이동, 즉 페이지 이동을 가능하게 해줍니다
                              • createStackNavigator 사용해보기
                                • [코드스니펫] 스택 네비게이터 설치 코드
                                • yarn add @react-navigation/stack
                                • [실습 ✍️] 적용하기</aside>
                                  • [코드스니펫] StackNavigator.js 스택 네비게이터
                                  • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
                                  <aside> 👉 워워 진정하세요! 위에서부터 차근차근 살펴볼거에요 🙂 언제나 우린 미니카 조립 설명서를 보면서 조립하듯, 공식 문서 내용 그대로 적용한다는 것 꼭 잊지마시고! 살펴보도록 하겠습니다.
                                  • 💻스택 네비게이터 코드 분석
                                    • 공식 문서 링크
                                    • 적용 순서 1) 사용 준비
                                    • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator();
                                    • 적용 순서 2) 기본 틀
                                    • //리액트의 모~든 파일은 컴포넌트라 생각하고 //페이지 기능을 해주는 모든 기능이 담겨 있는 컴포넌트를 만든다 생각하세요! const StackNavigator = () =>{ return ( /// 페이지 기능이 들어갈 곳 ) } export default StackNavigator;
                                    • 적용 순서 3) 스크린 옵션
                                    • //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
                                    • 적용 순서 4) 페이지 연결
                                    • {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/>
                                  <aside> 👉 컴포넌트를 페이지화 했고, 페이지를 이동할 수 있는 네이게이션도 준비가 됐다면, 우리는 최상단 컴포넌트 즉 App.js에 네비게이션 기능을 달아야 합니다.</aside>
                                  • [코드스니펫] App.js
                                  • import React from 'react'; //이제 모든 페이지 컴포넌트들이 끼워져있는 책갈피를 메인에 둘예정이므로 //컴포넌트를 더이상 불러오지 않아도 됩니다. // import MainPage from './pages/MainPage'; // import DetailPage from './pages/DetailPage'; import { StatusBar } from 'expo-status-bar'; //메인에 세팅할 네비게이션 도구들을 가져옵니다. import {NavigationContainer} from '@react-navigation/native'; import StackNavigator from './navigation/StackNavigator' export default function App() { console.disableYellowBox = true; return ( <NavigationContainer> <StatusBar style="black" /> <StackNavigator/> </NavigationContainer>); }
                                  • 스택 네비게이터 적용 후 MainPage 모습
                                • 즉, 앱 가장 최상위 코드에 네비게이션을 다는겁니다 그래야 앱 어디서든 원하는 페이지 이동이 가능할테니까요!
                                • </aside>
                                • <aside> 👉 navigation 폴더 하나를 만들고 StackNavigator.js 파일을 만들어주세요 그리고 StackNavigator 안에 다음 코드를 넣어주세요
                              • 페이지 헤더 수정</aside><aside> 💡 그 이유는 StackNavigator에서 현재 헤더에 대한 스타일과 페이지에 헤더의 제목까지 결정해주고 있기 때문입니다.
                                • 스택 네비게이터의 헤더 스타일 부분 코드
                                • <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTintColor: "#fff", headerBackTitleVisible: false }} > {/* name에 해당 하는 부분이 페이지의 타이틀이 됩니다.*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
                                <aside> 💡 따라서 헤더 스타일을 적절히 바꾸기 위해 다음과 같이 옵션 부분의 코드를 수정해보도록 하겠습니다.
                                • [코드스니펫] StackNavigator.js
                                • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
                                • [코드스니펫] MainPage.js 에서 title 삭제!
                                • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                • StackNavigator 옵션 코드 수정 후 모습
                                <aside> 💡 그래도 위에 페이지 제목으로 MainPage가 되는 모습은 뭔가 또 수정이 필요해보입니다. 이를 위해선 다음 스택 네비게이션 페이지 이동 강의에서 살펴보도록 하겠습니다
                              • </aside>
                              • </aside>
                              • </aside>
                              • <aside> 💡 그런데 현재 MainPage 화면은 다음과 같이 상단의 모습이 약간 이상해 보입니다.
                              • 페이지 이동하기일단 페이지를 이동시키려면, 책갈피가 페이지들에게 부여해준 페이지 이동 기능을 사용해야 합니다.</aside><aside> 💡 일단 먼저 구조를 보여드렸습니다. 물론 사용을 해봐야 감이 오겠죠?같이 해보죠!
                                • [실습 ✍️] 데이터 없이 페이지 이동하기
                                  • [코드스니펫] MainPage.js
                                  • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (**<Card content={content} key={i} navigation={navigation}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                  • [코드스니펫] Card.js
                                  • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 **export default function Card({content,navigation}){** return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage')}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                                • navigation.navigate("DetailPage")
                                • [실습 ✍️] 데이터 가지고 페이지 이동하기</aside><aside> 💡 그럼 Card에서 DetailPage로 이동할 때, MainPage로 부터 넘겨받은 content도 넘겨볼까요?</aside>
                                  • [코드스니펫] Card.js
                                  • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function Card({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage',**content**)}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                                  • [코드스니펫] DetailPage.js
                                  • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage({navigation,route}) { //초기 컴포넌트의 상태값을 설정 //state, setState 뿐 아니라 이름을 마음대로 지정할 수 있음! const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) //Card.js에서 navigation.navigate 함수를 쓸때 두번째 인자로 content를 넘겨줬죠? //content는 딕셔너리 그 자체였으므로 route.params에 고대~로 남겨옵니다. //즉, route.params 는 content죠! navigation.setOptions({ //setOptions로 페이지 타이틀도 지정 가능하고 title:route.params.title, //StackNavigator에서 작성했던 옵션을 다시 수정할 수도 있습니다. headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                                  • 이동한 디테일 페이지 모습
                                  <aside> 💡 건네 받은 값을 꺼낼 땐, 다음과 같이 책갈피가 navigation과 추가적으로 건네준 route에서 꺼내 확인 할 수 있습니다.</aside>
                                  • route에 담겨져 오는 데이터 콘솔에서 직접 확인
                                  • DetailPage에서 상태값을 초기에 설정한 이유</aside><aside> 💡 그 이유는 컴포넌트가 화면에 그려지는 순서에 있습니다. 이건 지금 단계에선 조금 과한감이 있어서 간략히만 설명해보면 다음과 같습니다.
                                    1. DetailPage 컴포넌트가 useState에 들어 있는 
                                       데이터 가지고 화면에 그려짐(return 함수실행)
                                    2. 화면에 다 그려진후, useEffect 함수 실행
                                    3. useEffect에서 상태값 변경 이벤트가 실행되면 변경된 데이터 가지고 다시 return 실행
                                    4. 변경된 데이터를 가지고 화면에 DetailPage가 다시 그려짐.
                                    
                                    <aside> 💡 곰곰히 생각해보면 결국, 리액트 네이티브에서 화면이 변경되는 시점은 컴포넌트의 상태값이 변경될 때니까요!또는! 우리가 한번 배웠떤 Loading.js를 이용하여 데이터가 준비가 되면 로딩 화면을 치우고, 본 화면을 보여준다던가!</aside>
                                  • 여러분들 마음껏 화면 처리를 해주시면 됩니다!
                                  • 이 개념은 어려울 수 있습니다 충분히! 따라서 처음에는, 어떠한 데이터를 보여주는 컴포넌트라면, 무조건 초기값을 의미없는 값이더라도 넣고 시작한다! 라고 생각하시면 편합니다!
                                  • </aside>
                                  • //초기 컴포넌트의 상태값을 설정 const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" })
                                  • <aside> 💡 DetailPage 초반에 우린 이렇게 상태값을 설정해놨었습니다. 그 이유가 뭘까요? 심지어 이 상태값을 초기에 설정안하면 오류가 발생합니다. tip엔 아무것도 없다며...
                                • route.params 객체에 건네준 딕셔너리가 넘겨 있습니다!
                                • 왜냐하면 지금은 어떠한 카드를 눌러도 상세 페이지에서 동일한 데이터를 보게 되잖아요? tip 데이터를 고정해놨으니까요!
                                • navigation.navigate("Detail",**{** title**:** title **})**
                                • <aside> 💡 버튼 카드에서 사용한 함수에 아래와 같이 두 번째 인자로 딕셔너리를 넘겨주면, 우리는 이동 한 페이지에서 넘겨준 데이터를 받을 수 있습니다.
                              • </aside>
                              • 그럼 이제 Card.js에 페이지 이동 기능을 달아보겠습니다. 코드를 실행 한 다음 페이지를 이동해 보면 익숙한 뒤로 가기 버튼도 보일거에요!
                              • //navigation 객체가 가지고 있는 두 함수(setOptions와 navigate) //해당 페이지의 제목을 설정할 수 있음 navigation.setOptions({ title:'나만의 꿀팁' }) //Stack.screen에서 name 속성으로 정해준 이름을 지정해주면 해당 페이지로 이동하는 함수 navigation.navigate("DetailPage") //name 속성을 전달해주고, 두 번째 인자로 딕셔너리 데이터를 전달해주면, Detail 페이지에서 //두번째 인자로 전달된 딕셔너리 데이터를 route 딕셔너리로 로 받을 수 있음 navigation.navigate("DetailPage",**{ title: title }**) //전달받은 데이터를 받는 route 딕셔너리 //비구조 할당 방식으로 route에 params 객체 키로 연결되어 전달되는 데이터를 꺼내 사용 //navigate 함수로 전달되는 딕셔너리 데이터는 다음과 같은 모습이기 때문입니다. /* { route : { params :{ **title:title** } } } */ **const {** title**} = route.params;**
                              • Stack.screen에 등록된 모든 페이지 컴포넌트들은 navigation 와 route 라는 딕셔너리(객체)를 속성으로 넘겨받아 사용할 수 있습니다. 이 두 딕셔너리는 다음과 같은 기능을 갖습니다.
                              • <aside> 💡 Main 페이지(MainPage.js)에서 카드 버튼을 누르면(Card.js) 꿀팁 상세 페이지(DetailPage.js)로 이동합니다
                              06**. [Expo 앱다운 앱기능] 공유 기능과 외부 링크 추가**
                              • Share그런데 결과를 친구한테 공유하고 싶은 마음이 들수도 있겠네요 결과를 공유해주고, 앱다운로드 주소까지 전달해준다면 여러분이 만든 앱 홍보하기가 수월하겠죠?
                              • </aside>
                              • <aside> 👉 디테일 페이지까지 이동해서 도달했다면 여러분은 앱 개발자!
                              • Share 적용해보기
                                • 준비!
                                  • 설치 할 라이브러리 없이, react-native에서 기본적으로 제공해주는 공유 기능을 사용합니다.
                                  import { Share } from "react-native";
                                  
                                  • 그리고 우린 다음과 같은 공유 버튼을 디테일 페이지 하단에 생성하고 기능을 만들꺼에요! 한번 잠깐 영상을 멈추고 기능은 생각 말고 다음 버튼을 추가한 화면만 그려보세요! 복습 복습
                                  • 버튼 추가 디테일 페이지 모습
                                • [실습 ✍️] 적용</aside>
                                  • [코드스니펫] DetailPage.js
                                  • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { **Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); }** return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}**>팁 공유하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:100, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                                  • 공유하는 모습
                                • <aside> 💡 share 함수를 만들고 거기에 react-native가 기본적으로 제공해주는 Share 함수를 사용하여 간단히 공유를 할 수 있습니다
                                • 공유 모습
                              • Linking이번엔 앱에서 외부 링크를 여는 방법에 대해 배워봅니다. 버튼을 누르면, 버튼에 연결 시킨 기능을 통해 외부 링크를 핸드폰에 있는 기본 브라우저로 열어봅니다.
                              • </aside>
                              • <aside> 👉 디테일 페이지에 있는 내용들이 사실 어디에서 가져온거라면? 출처를 남겨야 할겁니다. 그리고 그 출처로 바로가기 버튼정도도 있으면 여러분들이 만드는 다양한 플랫폼들을 서로 연결 시킬 수 있겠죠?
                              • Linking 적용해보기
                                • 준비!
                                  • DetailPage에 버튼을 다음과 같이 추가해주세요
                                  • 링크 버튼 추가 된 DetailPage 모습
                                  • expo 에서 제공해주는 도구를 설치 한다음, 해당 도구를 상단에 가져와 준비해야 합니다
                                  expo install expo-linking
                                  
                                  import * as Linking from 'expo-linking';
                                  
                                • [실습 ✍️] 적용
                                  • [코드스니펫] DetailPage.js
                                  • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; import * as Linking from 'expo-linking'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); } const link = () => { Linking.openURL("<https://spartacodingclub.kr>") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}>팁 공유하기 link()}>외부 링크 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:90, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                              07**. 3주차 끝 & 숙제 설명**
                                1. 어바웃 화면 페이지화 시키고 버튼 추가하기!
                                <aside> 👉 지난번 숙제로 만든 AboutPage.js를 아직 우린 스택 네비게이터에 연결하지 않았습니다.추가적으로 외부 링크 걸기까지 배웠으니, 여러분 인스타나 블로그 주소 혹은 아무주소를 AboutPage 하단 버튼에 연결해주세요!
                                • 만들 화면
                                • 가이드
                                  1. 메인에 TouchableOpacity 버튼을 두고 navigate를 연결시키면 이동이 가능하겠죠?
                                  2. Stack.screen에 AboutPage.js를 연결시켜야합니다!
                                  3. 소개 페이지 상태바의 디테일함을 주목하세요! 흰색이네요 🙂
                              • </aside>
                              • 따라서 여러분이 직접! 스택네비게이터에 AboutPage.js를 달아보구요! 하단 메인페이지의 상단에 다음과 같은 버튼을 만들어서 누르면 AboutPage로 가게끔 해주세요!
                                1. 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요
                              • 가이드
                                  1. 찜 데이터 제공
                                  <aside> 💡 나만의 꿀 팁 앱에서 찜 페이지란 쉽게 말해 팁 리스트 중에 선택한 팁들을 모아보는 페이지입니다. 이런 찜 페이지 자주 보셨죠?
                                  • 즉, 다음 상태값으로 화면을 구성해주세요::: 리스트에 딕셔너리 두 개가 들어있는 데이터입니다.
                                  </aside>
                                  • [코드스니펫] 찜 상태 데이터
                                  • const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }])
                                • 하지만 아직 데이터를 저장하고, 수정하고 등등의 데이터 관리 부분을 배우지 않았기 때문에 [코드스니펫] 찜 상태 데이터를 가지고 화면만 그려볼겁니다.
                                  1. 파일의 위치와 이름
                                  <aside> 💡 새로운 페이지를 만드는 중이니, 어디에 위치해야 할까요? 당연히 pages 폴더에 위치시켜야 겠죠?</aside>
                                • 또한 파일이름은 우리 LikePage.js 로 만들어 봅시다! ZzimPage는 좀...
                                  1. 메인의 꿀팁찜 버튼을 누르면 찜 페이지로!
                                  <aside> 💡 혼자만의 큰그림이 있었습니다. 메인페이지에 꿀팁찜 버튼이란걸 강의 초반부터 만들어 놓았었어요! 이 버튼을 누르면 찜 페이지로 가게끔 해봅시다!
                                • </aside>
                                  1. Card.js 를 복붙해서 찜 페이지에서만 사용하는 LikeCard.js 만들기
                                  <aside> 💡 찜 목록도 Card.js와 크게 다를건 없습니다. 기존 카드에 찜 삭제 버튼정도가 추가 되었다는 점?이 다르므로, Card.js 코드를 복붙해서 LikeCard.js를 만들어 봅시다</aside>
                                • 그런데! LikeCard.js에선 TouchableOpacity로 감싸는게 아닌! View 태그로 카드 전체를 감싸주도록 바꿔주세요!
                                  1. 최종 폴더 & 파일 모습
                              HW. 3주차 숙제 해설
                              • 숙제 1: 어바웃 화면 페이지화 시키고 버튼 추가하기!
                                • [코드스니펫] StackNavigator.js(숙제1)
                                • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> </Stack.Navigator> ) } export default StackNavigator;
                                • [코드스니펫] AboutPage.js(숙제1)
                                • import React,{useEffect} from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' import { StatusBar } from 'expo-status-bar'; export default function AboutPage({navigation,route}){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" useEffect(()=>{ navigation.setOptions({ title:"소개 페이지", headerStyle: { backgroundColor: '#1F266A', shadowColor: "#1F266A", }, headerTintColor: "#fff", }) },[]) return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:20, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
                                • [코드스니펫] MainPage.js(숙제1)
                                • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20, }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
                              • 숙제 2: 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요!
                                • [코드스니펫] StackNavigator.js(숙제2)
                                • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; import LikePage from '../pages/LikePage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> <Stack.Screen name="LikePage" component={LikePage}/> </Stack.Navigator> ) } export default StackNavigator;
                                • [코드스니펫] LikeCard.js(숙제2)
                                • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function LikeCard({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <View style={styles.card}> <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> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                                • [코드스니펫] MainPage.js(숙제2)
                                • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> **<TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>** </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
                                • [코드스니펫] LikePage.js(숙제2)
                                • import React,{useState, useEffect} from 'react'; import {ScrollView, Text, StyleSheet} from 'react-native'; import LikeCard from '../components/LikeCard'; import Card from '../components/Card'; export default function LikePage({navigation,route}){ const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }]) useEffect(()=>{ navigation.setOptions({ title:'꿀팁 찜' }) }) return ( { tip.map((content,i)=>{ return() }) } ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#fff" } })01. 오늘 배울 것
                                  • 오늘 배울 내용이 두 가지는 앞으로도 계속~ 반복 숙달합니다 🙂
                                    1. 리액트 필수 지식 활용
                                    2. Expo 기능 사용
                                    3. 페이지 적용
                                    </aside>
                                  • 오늘은 앱다운 앱을 만드는 연습과 기술을 배울텐데요! 다음 순서대로 지금까지 배웠던 것을 복습 하면서 앱다운 앱 기능을 배우니 점점 더 여러분은 앱 개발에 숙달되는 모습을 볼 수 있습니다!
                                  • <aside> 👉 지난 시간까지 자바스크립트 기초체력을 다지고 JSX로 화면을 그리는 방법 까지 배워봤습니다.
                                  • [앱 필수 기초지식] 리액트 필수지식
                                    • 컴포넌트(Component)
                                    • 상태(State,useState)
                                    • 속성(Props)
                                    • useEffect
                                    이렇게 네 가지입니다. 단어가 조금 어려워 보이지만, 재미있고 간단한 개념들입니다. 이 개념들을 통해 앱 코드를 체계적으로 관리할 수 있고 화면안에서 데이터를 자유자재로 관리할 수도 있습니다.리액트 네이티브(React Native)는 리액트(React.js) 기반으로 만들어진 앱 개발 기술이기 때문이에요! 겁먹지마시고 차근차근 다루어봅시다.
                                  • </aside>
                                  • 최소한의 리액트 개념을 배우는 이유는
                                  • <aside> 👉 리액트 네이티브 앱을 만들기 위해서 알아야 하는 최소한의 리액트 개념을 배웁니다.
                                  • [Expo 앱다운 앱기능] 앱다운 앱을 위해 사용할 리액트 네이티브와 Expo 기능들이렇게 앱 다운 앱이 되기위한 기능들을 공식문서를 보고 하나하나 적용해보는 시간을 갖습니다. 이 연습을 거치면 여러분들은 공식문서를 보며 필요한 앱 기능들을 여러분들 앱에 붙여나갈 수 있습니다!
                                  • </aside>
                                  • <aside> 👉 앱이라고 하면 보통 현재 보고 있는 내용을 친구들한테 공유하거나, 앱 안에 있는 링크를 누르면 바로 해당 링크로 화면이 전환되는 기능들이 들어 있습니다. 또한 배터리를 볼 수 있는 상태바 스타일도 앱 전체적인 스타일에 맞게 변경할 수도 있어야 할겁니다.
                                  • [앱 페이지 적용] 앱에 페이지 기능 넣기!그래서 앱안에 여러 페이지를 두고 버튼을 눌러 이동할 수 있는 기능을 달아봅니다. 본격적으로 거의 온전한 앱의 모습을 띄게 됩니다!
                                  • </aside>
                                  • <aside> 👉 앱에 페이지가 하나라면 너무 단조롭고 볼게 없는 앱이 되겠죠?
                                  02. [함께해보기] 나만의 꿀팁 앱 상세 화면
                                  • [실습 ✍️ ] 나만의 꿀팁 상세 페이지가될 아래의 화면을 만들어보세요
                                  • 퀴즈 가이드
                                    • [현재 코드 상황]
                                      • App.js
                                      • import React from 'react' import MainPage from './pages/MainPage'; import AboutPage from './pages/AboutPage'; export default function App(){ // return (<MainPage/>) return (<AboutPage/>) }
                                      • MainPage.js
                                      • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { 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> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, 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", } });
                                      • AboutPage.js
                                      • import React from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' export default function AboutPage(){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:100, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
                                      • 폴더구조
                                    • DetailPage.js를 pages 폴더에 만들어 넣기</aside>
                                    • <aside> 👉 저번 1주차 마무리 시간에, pages 폴더를 만들고 AboutPage.js 파일을 만들어 넣었던 기억이 나시나요? 그때랑 동일하게 pages 폴더안에 DetailPage.js 파일을 만들고 App.js에서 return 부분에 DetailPage.js를 넣어주세요 다음과 같이요!
                                    • 문제 디테일 화면을 위한 데이터딕셔너리 형태로 키값에 접근해서 값을 꺼내 사용해도 되고! 문자열들 그대로 복사해서 Text 태그에 넣어 사용해도 됩니다!
                                      • [코드스니펫] 상세 화면에서 사용될 데이터
                                      • const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }
                                    • </aside>
                                    • <aside> 👉 화면에 나타낼 꿀 팁은 다음 데이터를 사용하세요.
                                  • 함께 해보기</aside>
                                    • [코드스니펫] DetailPage.js 화면
                                    • import React from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage() { const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" } const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                                  • <aside> 👉 DetailPage를 확인하려면 App.js를 좀 바꿔야 겠죠? 어떻게 바꿔야 할까요?
                                  03**.** [앱 필수 기초지식] 컴포넌트, 속성, 상태, useEffect
                                  • 공부 할 리액트 필수 지식<aside> 👉 다시 떠올리자면, 리액트 네이티브는 리액트(React.js) 라이브러리 기반으로 만들어진 프레임워크 입니다. 그렇기 때문에 기본적인 구조는 리액트를 닯아 있습니다.1) 컴포넌트(Component) : 정해진 엘리먼트들(요소)을 사용하여 만든 화면의 일부분 2) 상태(State) : 컴포넌트에서 데이터를 유지하고 관리하기 위한 유일한 방법 == 그냥 사용할 데이터! 3) 속성(Props) : 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하는 방식 == 그냥 데이터 전달! 4) useEffect : 화면에 컴포넌트가 그려지면 처음 실행해야 하는 함수들을 모아두는 곳</aside>
                                  • 네 가지가 있습니다. 워워 어렵지 않아요. 이것도 정해진 규칙과 개념일 뿐! 사용방법 숙지!만 하면 그만이랍니다!
                                  • 대표적으로
                                  • 컴포넌트(Component)</aside><aside> 👉 컴포넌트는 즉, 화면의 모든 부분입니다. 그리고 컴포넌트란 App.js의 큰 App 함수처럼, 코드 전체를 감싸고 있는 함수를 뜻하기도 합니다.</aside>이렇게 컴포넌트로 생각하고 앱을 개발해 나아간다면, 코드 재사용이 용이 해집니다.실제 우리 예제에 어떻게 적용할 수 있는지. 확인해보겠습니다.
                                  • </aside>
                                  • 코드 재사용이란 어려운 용어는 아니고, 버튼 하나를 만들었을 때 이 버튼 코드를 여러 페이지에서 사용 가능하다는 뜻입니다.
                                  • <aside> 👉 버튼 하나가 컴포넌트가 될 수 있고, 버튼을 모아둔 영역이 컴포넌트가 될 수 있습니다.
                                  • 따라서 App.js를 App 컴포넌트라고도 부를 수 있습니다.
                                  • <aside> 👉 컴포넌트는 UI의 요소, 요소를 재사용 가능한 부분으로 조각내서 운영하는 기법입니다. 리액트 기반으로 만들어진 페이스북 웹사이트는 운영되는 컴포넌트가 수 만가지라고 합니다
                                  • [실습 ✍️ ] 메인화면 컴포넌트화 해보기그럼 다음 코드를 Card.js에 넣어주세요! 어디서 많이 본 코드입니다 그쵸?<aside> 🚧 마찬가지로 MainPage.js 화면을 확인하려면 App.js를 바꿔야 겠죠? return (<MainPage/>) 으로요!
                                    • [코드스니펫] Card.js
                                    • import React from "react" import {View,Text,Image,StyleSheet} from "react-native"; //비구조 할당 방식으로 넘긴 속성 데이터를 꺼내 사용함 export default function Card({content}) { return (<View style={styles.card}> <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>) } const styles = StyleSheet.create({ 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", } })
                                    <aside> 👉 맞습니다! MainPage.js에서 이미지위에 텍스트가 있던, 한 줄에 세 개씩 나열 되었던 그 카드 버튼입니다!.</aside>
                                    • [코드스니펫] MainPage.js
                                    • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; **import Card from '../components/Card';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (**<Card content={content} key={i}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                    <aside> 👉 MainPage.js에 있던 코드들을 Card.js로 옮겼습니다. 이렇게 옮기게 되면, 추후에 MainPage.js가 아닌 다른 페이지(페이지 또한 컴포넌트로 볼 수 있습니다) 또는 컴포넌트에서 Card.js를 사용할 수 있습니다.</aside>
                                    • MainPage.js
                                    • Card.js
                                    <aside> 👉 미리 말씀드리자면 MainPage.js에서 Card.js로 데이터를 넘기고 있는 모습입니다! 이를 속성을 넘긴다! 라고 보통 일컫는데 이는 다음 챕터에서 더 자세히 다루도록 하겠습니다!
                                  • </aside>
                                  • 그런데 약간 생소한 부분이 있습니다 다음 두 부분인데요!
                                  • 그럼 MainPage.js는 다음과 같이 변경해주세요
                                  • </aside>
                                  • </aside>
                                  • <aside> 👉 Components 폴더 안에 Card.js라는 파일을 만듭니다. 보통 컴포넌트들은 components 폴더에 모아두면서 관리하곤 합니다.
                                  • 속성(Props)<Text> 태그엔 numberOfLines이란 속성이 있었습니다. 말줌임표 효과를 주었죠! <Image> 태그엔 resizeMode란 속성이 있었습니다. 이미지가 영역을 차지하는 방식을 나타냈죠.</aside>예를 들어 Card를 사용하는 곳에서<Card image={'이미지 주소'}/> 라고 속성을 써 넣으면, 이 <Card/>는 image라는 속성이름에 {'이미지 주소'} 값을 을 갖게 됩니다. 즉 키와 벨류의 값을 갖게 됩니다 근데 이게 무슨 의미가 있을까요?</aside>
                                  • 의미가 있습니다. 속성을 부여받은 컴포넌트에서 해당 속성 값을 받아서 사용 할 수 있거든요! 다음과 같이 말이죠!
                                  • <aside> 👉 우리가 만든 <Card/> 컴포넌트에도 속성이 존재합니다. 우리가 부여 하면 부여하는 대로 속성이 되는 겁니다.
                                  • 이 모든 속성들은 공식 문서에 나와 있고, 해당 엘리먼트들이 태어날 때부터 가지고 있는 속성이였습니다.
                                  • <aside> 👉 속성은 쉽게 생각해서 컴포넌트에 데이터를 전달한다는 것이다. 그 전달 모습은 키와 벨류의 형태입니다.
                                  • [확인해보기👀 ]</aside>
                                    • MainPage.js 에서의 Card.js 컴포넌트
                                    <aside> 👉 어렵지 않은 규칙이 숨어 있습니다!
                                    1. 컴포넌트에 속성(데이터)을 부여해줘서 전달할땐, 키와 벨류(content={content}) 형태로 전달해줘야 할 것
                                    2. 컴포넌트를 반복문 돌릴땐, 컴포넌트마다 고유하다는 것을 표현하기 위해, map에서 나오는 인덱스(i)를 key = {i} 속성 전달 형태로 꼭 넣을것! 입니다.
                                    </aside>
                                    • Card.js 에서의 속성 값 내려 받기
                                    <aside> 👉 오랜만에 복습겸 나온 비구조 할당 방식 의 모습입니다. MainPage.js에서 넘겨준 속성은 실제 받게되는 컴포넌트에서 정말 딕셔너리 데이터를 받았다! 라고 생각하면 됩니다.이 방식으로 넘겨준 키값을 {키,키,키} 비구조 할당 방식으로 바로 꺼내서 사용하면 됩니다!
                                  • </aside>
                                  • 때문에, 비구조 할당 방식이 뭐였죠? 딕셔너리에서 키값을 바로 취해서 변수로써 함수안에서 즉시 사용할 수 있는 방식이였죠?
                                  • <aside> 👉 MainPage.js에서 방금전 컴포넌트화! 했던 Card.js를 우린 이렇게 사용했습니다.
                                  • 상태(State, useState)<aside> 👉 컴포넌트마다 데이터를 보유하고 관리 할 수 있습니다. 데이터라고 불러도 되지만, 리액트에서는 컴포넌트에서 보유/관리 되는 데이터를 **상태**라 부릅니다.사용방법은 실습을 통해 알아보도록 하겠습니다!
                                  • </aside>
                                  • 리액트에서 상태(state)는 리액트 라이브러리에서 제공해주는 useState로 생성하고 setState 함수로 정/변경 할 수 있습니다.
                                  • useState, 그렇다면 왜 중요할까?여러분이 만드는 화면은 데이터에 따라 변경됩니다 또 아무 데이터가 아니라 이 상태(state)로 관리되는 데이터가 변경되면 화면이 바뀝니다.</aside><aside> 👉 당시 개발자들끼리 이 상태와 컴포넌트의 관계를 공식으로 표현한 글을 보고 대단하다!! 했던 기억이 있는데, 여러분들은 어떻게 느끼실지 궁금하네요 ㅎㅎ무슨말인지 아직 잘 와닿지 않으시죠? 다른건 잘 와닿지 않아도 state는 컴포넌트에서 관리되는 상태만 기억하시면 충분합니다 이 개념은 이번 강의에서 개념을 다 배운 뒤, 다음 6 강에서 실습 하며 직접 확인할 수 있습니다
                                  • </aside>
                                  • 저 공식은 즉, 사용자 화면(UI)는 컴포넌트(component)에 어떤 데이터(state)가 주입되고 변경되냐에 따라 변화된다를 뜻합니다
                                  • UI = component(state)
                                  • 모르셔도 되지만 꽤 유의미한 공식이 있습니다
                                  • <aside> 👉 리액트는 특이한 점이 있습니다.
                                  • 화면이 그려진다음 가장 먼저 실행되는 함수, useEffect화면이 그려진 다음 가장 먼저 실행되는 함수로 사용방법이 간단합니다 형식만 알고 있으면 충분합니다
                                    **useEffect(()=>{**
                                    
                                    	...화면이 그려진 다음 가장 먼저 실행되야 할 코드 작성 공간
                                    
                                    **},[])**
                                    
                                    <aside> 👉 useEffect(()=>{ },[])</aside>
                                  • 이 안에서, 화면이 그려진다음 실행시키고 싶은 함수를 작성한다면 가장먼저 실행이 됩니다. useEffect 또한 어떤 역할을 하는지만 알고! 바로 실습을 통해 확인해보도록 하겠습니다
                                  • </aside>
                                  • <aside> 👉 useEffect는 너무 간단하고 유용하게 쓰이는 리액트 기본 제공 함수입니다.
                                  • useEffect, 그렇다면 왜 중요할까?
                                    1. 화면이 그려진다
                                    2. useEffect가 데이터를 준비한다
                                    3. 상태 데이터가 업데이트 되었으니 화면이 다시 그려진다
                                    화면이 그려진 다음, 서버에게 필요한 데이터를 요청하여 받은 후, 화면을 다시그릴때 주로 사용되는데요! 아주 일반적인 패턴이므로 여러분들도 금방 이해하고 익숙해지게 됩니다!
                                  • </aside>
                                  • <aside> 👉 보통 useEffect는 데이터를 준비할 때 사용합니다 데이터를 준비한다는 것은, 데이터를 서버로부터 혹은 어디선가로부터 받은 후 상태(state)에 반영한다는 것을 뜻합니다. 이런순서로 말이죠
                                  • [실습✍️ ] data.json을 상태(state)에 넣어서 관리해보기복습하실때 주석도 자세히 살펴보세요!
                                    • [코드스니펫] MainPage.js
                                    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //useState 사용법 //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수 //setState는 state를 변경시킬때 사용해야하는 함수 //모두 다 useState가 선물해줌 //useState()안에 전달되는 값은 state 초기값 const [state,setState] = useState([]) //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수 //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음 useEffect(()=>{ setState(data) },[]) // let tip = data.tip; //data.json 데이터는 state에 담기므로 상태에서 꺼내옴 let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                    <aside> 👉 그러나 실행을 하면 다음과 같은 오류 화면이 나옵니다
                                    • 오류화면
                                    <aside> 👉 그 이유는 이런 절차로 발생됩니다.
                                    1. 화면이 그려진다
                                    2. useEffect가 데이터를 state에 useState를 이용하여 업데이트한다
                                    3. 상태(state)가 변경되었으니 화면이 다시 그려진다
                                    여기서 1번에서 오류가 난겁니다. 화면이 가장 먼저 그려질때, 우리가 Card에 데이터를 넘기기위해 어떤일을 했나요?이런 오류는 우리가 지금 배운 개념들, 상태, 컴포넌트, useEffect를 이용하여 로딩화면을 만들어줌으로써 해결이 가능합니다!
                                  • </aside>
                                  • 처음부터 데이터가 없어서 , 꺼내올수 없는 오류
                                  • <aside> 👉 상태(state)에서 tip 키 값에 접근하여 데이터를 꺼내 map 즉, 반복문을 돌리려했습니다. 그런데 state에 데이터가 처음부터 있었나요? 없었죠? useEffect를 거쳐야 state에 데이터가 담기는데 아직 없는 상태라 오류가 나는 겁니다.
                                  • </aside>
                                  • </aside>
                                  • </aside>
                                  • <aside> 👉 지금 꿀팁을 data.json 파일에서 꺼내 바로 JSX 문법에서 사용하고 있습니다. 이를 다음과 같이 상태에 저장한 다음 사용하도록 변경해보도록 하겠습니다.
                                  04. [앱 필수 기초지식 응용] 로딩화면, 카테고리 기능, 상태 바
                                  • 앱 운영에 필요한 로딩화면이때 우린 로딩화면을 만들어줌으로써 쉽게 해결이 가능합니다. 컴포넌트 폴더에 Loading.js 파일을 만들어주고 다음 코드를 넣어주세요
                                    • [코드스니펫] Loading.js
                                    • import React from 'react'; import {View,Text,StyleSheet} from 'react-native'; export default function Loading(){ return(<View style={styles.container}><Text style={styles.title}>준비중입니다...</Text></View>) } const styles = StyleSheet.create({ container: { //앱의 배경 색 flex:1, justifyContent:'center', alignItems:'center', backgroundColor: '#fdc453', }, title: { fontSize:20, fontWeight:'700' } })
                                    <aside> 👉 그리고 MainPage.js 다음 코드 스니펫을 넣어주세요
                                    • [코드스니펫] MainPage.js
                                    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 const [state,setState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. **const [ready,setReady] = useState(true)** useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 **setTimeout(()=>{ setState(data) setReady(false) },1000)** },[]) // let tip = data.tip; let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" **//처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐)** return **ready ? <Loading/>** : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                    • 적용 모습
                                    <aside> 👉 ready 라는 새로운 상태값이 추가되었습니다. 즉, 상태값은 컴포넌트안에 여러개가 될 수 있습니다.즉, 이런 순서로 진행된거에요!
                                    1. ready 값이 true이므로 return 구문에서 ? 물음표 바로 뒤의 Loading 컴포넌트가 화면에 그려짐
                                    2. 화면이 그려지고 난다음, 1초 이따가 상태값들이 채워지고 변경됨
                                    3. ready 상태 값이 false가 됨
                                    4. 상태값이 변경되었으므로 화면이 다시 그려짐
                                    5. ready 값이 false 이므로 return 구문에서 : 콜론 뒤의 MainPage 컴포넌트가 화면에 그려짐
                                    차근차근 순서따라 반복해서 보면 별거아닙니다. 정해진 규칙에 따라 우린 코드를 적용했고 정해진 규칙에 따라 코드가 실행되고 있는 거에요!
                                  • </aside>
                                  • useEffect안에 setTimeout이라는 함수가 존재합니다. , 뒤에 있는 숫자 (1000 === 1초) 만큼, 지연됐다가 안에 있는 코드가 실행되는 지연 함수입니다.
                                  • </aside>
                                  • </aside>
                                  • <aside> 👉 5장에서 오류가 났던 것 처럼 화면이 그려질때 준비된 데이터가 없다!? 이러한 상황은 아주 빈번합니다.
                                  • 카테고리 버튼상태값이 바뀌면 화면이 다시 그려진다! 라는 개념을 염두하고 한번 만들어보겠습니다!
                                  • </aside>
                                  • <aside> 👉 우리에게 이런 버튼이 있었습니다. 카테고리 버튼인데요! 이 버튼은 장식이아니라, 실제 누르면 하단의 데이터가 카테고리에 맞게 다시 정렬되는 기능이 의도되어 있습니다.
                                  • [실습✍️ ] 카테고리 기능 넣기그리곤 함수가 필요합니다. 우리가 만든, 생활 재테크 반려견 버튼에 연결할 함수인데요!? 이 함수는 카테고리에 따라 카테고리 상태데이터를 새롭게 구성해주는 기능을 합니다.
                                    • [코드스니펫] MainPage.js
                                    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 **const [state,setState] = useState([])** //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 **const [cateState,setCateState] = useState([])** //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 **let tip = data.tip; setState(tip) setCateState(tip) setReady(false)** },1000) },[]) **const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } }** let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> **<TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>** <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, **middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 },** 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" }, **middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" },** cardContainer: { marginTop:10, marginLeft:10 }, });
                                    • 제태크를 눌렀을때의 모습
                                  • </aside>
                                  • <aside> 👉 카테고리 기능을 위해선 먼저 카테고리 상태가 필요합니다. 카테고리에 맞는 꿀팁들을 지속적으로 저장하고 관리할 상태 그릇이죠!
                                  • Expo에서 제공해주는 앱다운 앱 기능들</aside>
                                  • <aside> 💡 Expo SDK. 일명 Expo에서 제공해주는 앱 기능 도구들을 개발할 때 여기서 확인 할 수 있습니다. (링크) 필요한 기능들이 있는지 목록을 쭉 보고, 원하는 기능을 선택해서 적용하면 끝!
                                  • 상태 바란?</aside><aside> 💡 배터리가 충분한지, 몇시인지 매일 보시죠? 이부분도 우리가 처리를 할 수 있습니다.
                                  • </aside>
                                  • <aside> 💡 앱이 앱에 따라 모바일 맨 위 상태 바가 변하는 앱이 있습니다. 상태바란 이런 겁니다. 여러분들 매일 같이 보는 부분이에요!
                                  • StatusBar
                                    • 본격적으로 라이브러리 설치 시작!</aside><aside> 👉 터미널 우측 버튼중에 분할이란 버튼이 있습니다.</aside>
                                    • 여러분들 터미널 왼편은 서버를 켜고 끄고를 담당하고(expo start) 터미널 우측은 필요한 라이브러리들을 설치할 때 명령어를 치는 장소로 분할해서 사용하면 편리합니다!
                                    • <aside> 👉 이제 본격적으로 라이브러리들을 설치합니다. vscode에서 터미널을 이렇게 변경해주세요
                                    • [코드스니펫] Expo 상태 바 설치
                                    • expo install expo-status-bar
                                    • [실습 ✍️] 적용적용 모습을 살펴볼까요?
                                      • [코드스니펫] MainPage에 StatusBar 설치
                                      • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; **import { StatusBar } from 'expo-status-bar';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> **<StatusBar style="black" />** <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                      <aside> 💡 style이 light일 때와, black일 때가 다릅니다. 우린 앱 화면 배경색을 검은색으로 설정했기 떄문에, 상태바를 black으로 하면 보이지 않습니다.Status Bar 공식문서
                                      • black 일때
                                      • light 일때
                                    • </aside>
                                    • 상태 바 속성은 공식문서에 다양하게 존재하니, 살펴보면서 앱에 적합한 상태 바를 적용해보세요!
                                    • </aside>
                                    • <aside> 💡 컴포넌트마다 다르게 적용할 수도 있고, 앱 전체에 공통적으로 적용할 수 있습니다. StatusBar를 컴포넌트 각각 다르게 둘 수 있습니다 우린 MainPage.js에 달아볼거에요!
                                  05**. [앱** 페이지 적용**] 네비게이터 사용하기**
                                  • 네비게이션이란?<aside> 👉 앱에 페이지 개념을 입혀주고! 웹 사이트를 이용하듯, 앱에서 여러분들이 만든 컴포넌트들을 페이지화 시켜주고, 해당 페이지끼리 이동을 가능하게 해주는 라이브러리입니다가령 다음 화면과 같이, 앱 하단에 탭 버튼을 두고 해당 버튼을 누르면 연결되어 있는 페이지로 바로바로 이동할 수 있게 해주는 기능(열 앱에서 많이 보셨죠?)도 가지고 있습니다.</aside>
                                  • 따라서 이번시간에 같이 공부한 다음 틈틈히 다른 기능들도 적용해보세요!
                                  • 이번 강의에선 앱 내의 페이지 구성 및 이동까지 직접 적용해볼 텐데요!, 이 라이브러리는 다양한 기능들을 가지고 있습니다.
                                  • <aside> 💡 결국 이 라이브러리도 Expo에서 지원하고 있는 도구로써, 앱을 만들기 위해 사용 중인 Expo와 궁합이 아주 잘 맞습니다.
                                  • </aside>
                                  • <aside> 💡 물론 이것 또한 쉽게 페이징을 도와주는 외부 라이브러리를 가져다가 사용합니다. react-navigation 공식문서 보러가기 —> (링크)
                                  • </aside>
                                  • 현재 갖추고 있는 페이지 구성 확인!</aside>
                                  • <aside> 💡 페이지 구성은 보통 pages 폴더 안에 생성해둔 컴포넌트로 구성합니다. 우리는 이미, MainPage.js 메인 페이지 DetailPage.js 상세 화면 페이지 그리고 숙제로 만든 소개화면인 AboutPage.js 까지 총 3 페이지를 가지고 있습니다.
                                  • 기본 설치 코드우리에게 필요한 것이라고 했지만, 거의 대부분의 앱에 적용이 되어 있는 것들만 가져왔습니다.아래 명령어를 차례차례 터미널에 넣어 실행해주세요 가장 마지막줄은 꽤 깁니다. 이렇게 여러 라이브러리들을 띄어쓰기로 연결해서 한번에 설치 할 수도 있습니다.
                                    • [코드스니펫] 네비게이션 설치 코드
                                    • yarn add @react-navigation/native
                                    • [코드스니펫] 네비게이션 추가 설치코드
                                    • expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
                                  • </aside>
                                  • 아래 명령어들은 네비게이션을 사용하기 위해 필요한 기본 라이브러리들이구요! 우리가 곧 배울 스택 네비게이션와 탭 네비게이션 기능은 추가적으로 라이브러리를 또 설치 해줘야 합니다 😂
                                  • <aside> 💡 리액트 네이티브에서 페이지 네비게이션을 구현하기란 조금 까다롭습니다. 그래서 딱 우리에게 필요한 것만 가져와 설치 하고, 적용해 나갈 계획입니다.
                                  • 스택 네비게이션이란?</aside><aside> 💡 컴포넌트를 페이지화 시키는 스택 네비게이션은 다음과 같습니다.이렇게 만든 여러 페이지들을 책갈피 기능을 하는 스택 네비게이터에 모조리 등록시켜서, 언제든지 이 페이지 이동이 가능하게끔 해줍니다</aside>
                                  • 페이지는 Stack.Screen 이라 부르며 책갈피는 Stack.Navigator라 부릅니다
                                  • 우리가 페이지처럼 만든 컴포넌트를, 정말 페이지처럼 사용할 수 있게끔 페이지로 컴포넌트를 감싸 페이지로 만들어줍니다.
                                  • <aside> 💡 스택 네비게이션은 컴포넌트에 페이지 기능을 부여해주고 컴포넌트에서 컴포넌트로 이동, 즉 페이지 이동을 가능하게 해줍니다
                                  • createStackNavigator 사용해보기
                                    • [코드스니펫] 스택 네비게이터 설치 코드
                                    • yarn add @react-navigation/stack
                                    • [실습 ✍️] 적용하기</aside>
                                      • [코드스니펫] StackNavigator.js 스택 네비게이터
                                      • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
                                      <aside> 👉 워워 진정하세요! 위에서부터 차근차근 살펴볼거에요 🙂 언제나 우린 미니카 조립 설명서를 보면서 조립하듯, 공식 문서 내용 그대로 적용한다는 것 꼭 잊지마시고! 살펴보도록 하겠습니다.
                                      • 💻스택 네비게이터 코드 분석
                                        • 공식 문서 링크
                                        • 적용 순서 1) 사용 준비
                                        • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator();
                                        • 적용 순서 2) 기본 틀
                                        • //리액트의 모~든 파일은 컴포넌트라 생각하고 //페이지 기능을 해주는 모든 기능이 담겨 있는 컴포넌트를 만든다 생각하세요! const StackNavigator = () =>{ return ( /// 페이지 기능이 들어갈 곳 ) } export default StackNavigator;
                                        • 적용 순서 3) 스크린 옵션
                                        • //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
                                        • 적용 순서 4) 페이지 연결
                                        • {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/>
                                      <aside> 👉 컴포넌트를 페이지화 했고, 페이지를 이동할 수 있는 네이게이션도 준비가 됐다면, 우리는 최상단 컴포넌트 즉 App.js에 네비게이션 기능을 달아야 합니다.</aside>
                                      • [코드스니펫] App.js
                                      • import React from 'react'; //이제 모든 페이지 컴포넌트들이 끼워져있는 책갈피를 메인에 둘예정이므로 //컴포넌트를 더이상 불러오지 않아도 됩니다. // import MainPage from './pages/MainPage'; // import DetailPage from './pages/DetailPage'; import { StatusBar } from 'expo-status-bar'; //메인에 세팅할 네비게이션 도구들을 가져옵니다. import {NavigationContainer} from '@react-navigation/native'; import StackNavigator from './navigation/StackNavigator' export default function App() { console.disableYellowBox = true; return ( <NavigationContainer> <StatusBar style="black" /> <StackNavigator/> </NavigationContainer>); }
                                      • 스택 네비게이터 적용 후 MainPage 모습
                                    • 즉, 앱 가장 최상위 코드에 네비게이션을 다는겁니다 그래야 앱 어디서든 원하는 페이지 이동이 가능할테니까요!
                                    • </aside>
                                    • <aside> 👉 navigation 폴더 하나를 만들고 StackNavigator.js 파일을 만들어주세요 그리고 StackNavigator 안에 다음 코드를 넣어주세요
                                  • 페이지 헤더 수정</aside><aside> 💡 그 이유는 StackNavigator에서 현재 헤더에 대한 스타일과 페이지에 헤더의 제목까지 결정해주고 있기 때문입니다.
                                    • 스택 네비게이터의 헤더 스타일 부분 코드
                                    • <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTintColor: "#fff", headerBackTitleVisible: false }} > {/* name에 해당 하는 부분이 페이지의 타이틀이 됩니다.*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
                                    <aside> 💡 따라서 헤더 스타일을 적절히 바꾸기 위해 다음과 같이 옵션 부분의 코드를 수정해보도록 하겠습니다.
                                    • [코드스니펫] StackNavigator.js
                                    • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
                                    • [코드스니펫] MainPage.js 에서 title 삭제!
                                    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                    • StackNavigator 옵션 코드 수정 후 모습
                                    <aside> 💡 그래도 위에 페이지 제목으로 MainPage가 되는 모습은 뭔가 또 수정이 필요해보입니다. 이를 위해선 다음 스택 네비게이션 페이지 이동 강의에서 살펴보도록 하겠습니다
                                  • </aside>
                                  • </aside>
                                  • </aside>
                                  • <aside> 💡 그런데 현재 MainPage 화면은 다음과 같이 상단의 모습이 약간 이상해 보입니다.
                                  • 페이지 이동하기일단 페이지를 이동시키려면, 책갈피가 페이지들에게 부여해준 페이지 이동 기능을 사용해야 합니다.</aside><aside> 💡 일단 먼저 구조를 보여드렸습니다. 물론 사용을 해봐야 감이 오겠죠?같이 해보죠!
                                    • [실습 ✍️] 데이터 없이 페이지 이동하기
                                      • [코드스니펫] MainPage.js
                                      • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (**<Card content={content} key={i} navigation={navigation}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                      • [코드스니펫] Card.js
                                      • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 **export default function Card({content,navigation}){** return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage')}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                                    • navigation.navigate("DetailPage")
                                    • [실습 ✍️] 데이터 가지고 페이지 이동하기</aside><aside> 💡 그럼 Card에서 DetailPage로 이동할 때, MainPage로 부터 넘겨받은 content도 넘겨볼까요?</aside>
                                      • [코드스니펫] Card.js
                                      • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function Card({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage',**content**)}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                                      • [코드스니펫] DetailPage.js
                                      • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage({navigation,route}) { //초기 컴포넌트의 상태값을 설정 //state, setState 뿐 아니라 이름을 마음대로 지정할 수 있음! const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) //Card.js에서 navigation.navigate 함수를 쓸때 두번째 인자로 content를 넘겨줬죠? //content는 딕셔너리 그 자체였으므로 route.params에 고대~로 남겨옵니다. //즉, route.params 는 content죠! navigation.setOptions({ //setOptions로 페이지 타이틀도 지정 가능하고 title:route.params.title, //StackNavigator에서 작성했던 옵션을 다시 수정할 수도 있습니다. headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                                      • 이동한 디테일 페이지 모습
                                      <aside> 💡 건네 받은 값을 꺼낼 땐, 다음과 같이 책갈피가 navigation과 추가적으로 건네준 route에서 꺼내 확인 할 수 있습니다.</aside>
                                      • route에 담겨져 오는 데이터 콘솔에서 직접 확인
                                      • DetailPage에서 상태값을 초기에 설정한 이유</aside><aside> 💡 그 이유는 컴포넌트가 화면에 그려지는 순서에 있습니다. 이건 지금 단계에선 조금 과한감이 있어서 간략히만 설명해보면 다음과 같습니다.
                                        1. DetailPage 컴포넌트가 useState에 들어 있는 
                                           데이터 가지고 화면에 그려짐(return 함수실행)
                                        2. 화면에 다 그려진후, useEffect 함수 실행
                                        3. useEffect에서 상태값 변경 이벤트가 실행되면 변경된 데이터 가지고 다시 return 실행
                                        4. 변경된 데이터를 가지고 화면에 DetailPage가 다시 그려짐.
                                        
                                        <aside> 💡 곰곰히 생각해보면 결국, 리액트 네이티브에서 화면이 변경되는 시점은 컴포넌트의 상태값이 변경될 때니까요!또는! 우리가 한번 배웠떤 Loading.js를 이용하여 데이터가 준비가 되면 로딩 화면을 치우고, 본 화면을 보여준다던가!</aside>
                                      • 여러분들 마음껏 화면 처리를 해주시면 됩니다!
                                      • 이 개념은 어려울 수 있습니다 충분히! 따라서 처음에는, 어떠한 데이터를 보여주는 컴포넌트라면, 무조건 초기값을 의미없는 값이더라도 넣고 시작한다! 라고 생각하시면 편합니다!
                                      • </aside>
                                      • //초기 컴포넌트의 상태값을 설정 const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" })
                                      • <aside> 💡 DetailPage 초반에 우린 이렇게 상태값을 설정해놨었습니다. 그 이유가 뭘까요? 심지어 이 상태값을 초기에 설정안하면 오류가 발생합니다. tip엔 아무것도 없다며...
                                    • route.params 객체에 건네준 딕셔너리가 넘겨 있습니다!
                                    • 왜냐하면 지금은 어떠한 카드를 눌러도 상세 페이지에서 동일한 데이터를 보게 되잖아요? tip 데이터를 고정해놨으니까요!
                                    • navigation.navigate("Detail",**{** title**:** title **})**
                                    • <aside> 💡 버튼 카드에서 사용한 함수에 아래와 같이 두 번째 인자로 딕셔너리를 넘겨주면, 우리는 이동 한 페이지에서 넘겨준 데이터를 받을 수 있습니다.
                                  • </aside>
                                  • 그럼 이제 Card.js에 페이지 이동 기능을 달아보겠습니다. 코드를 실행 한 다음 페이지를 이동해 보면 익숙한 뒤로 가기 버튼도 보일거에요!
                                  • //navigation 객체가 가지고 있는 두 함수(setOptions와 navigate) //해당 페이지의 제목을 설정할 수 있음 navigation.setOptions({ title:'나만의 꿀팁' }) //Stack.screen에서 name 속성으로 정해준 이름을 지정해주면 해당 페이지로 이동하는 함수 navigation.navigate("DetailPage") //name 속성을 전달해주고, 두 번째 인자로 딕셔너리 데이터를 전달해주면, Detail 페이지에서 //두번째 인자로 전달된 딕셔너리 데이터를 route 딕셔너리로 로 받을 수 있음 navigation.navigate("DetailPage",**{ title: title }**) //전달받은 데이터를 받는 route 딕셔너리 //비구조 할당 방식으로 route에 params 객체 키로 연결되어 전달되는 데이터를 꺼내 사용 //navigate 함수로 전달되는 딕셔너리 데이터는 다음과 같은 모습이기 때문입니다. /* { route : { params :{ **title:title** } } } */ **const {** title**} = route.params;**
                                  • Stack.screen에 등록된 모든 페이지 컴포넌트들은 navigation 와 route 라는 딕셔너리(객체)를 속성으로 넘겨받아 사용할 수 있습니다. 이 두 딕셔너리는 다음과 같은 기능을 갖습니다.
                                  • <aside> 💡 Main 페이지(MainPage.js)에서 카드 버튼을 누르면(Card.js) 꿀팁 상세 페이지(DetailPage.js)로 이동합니다
                                  06**. [Expo 앱다운 앱기능] 공유 기능과 외부 링크 추가**
                                  • Share그런데 결과를 친구한테 공유하고 싶은 마음이 들수도 있겠네요 결과를 공유해주고, 앱다운로드 주소까지 전달해준다면 여러분이 만든 앱 홍보하기가 수월하겠죠?
                                  • </aside>
                                  • <aside> 👉 디테일 페이지까지 이동해서 도달했다면 여러분은 앱 개발자!
                                  • Share 적용해보기
                                    • 준비!
                                      • 설치 할 라이브러리 없이, react-native에서 기본적으로 제공해주는 공유 기능을 사용합니다.
                                      import { Share } from "react-native";
                                      
                                      • 그리고 우린 다음과 같은 공유 버튼을 디테일 페이지 하단에 생성하고 기능을 만들꺼에요! 한번 잠깐 영상을 멈추고 기능은 생각 말고 다음 버튼을 추가한 화면만 그려보세요! 복습 복습
                                      • 버튼 추가 디테일 페이지 모습
                                    • [실습 ✍️] 적용</aside>
                                      • [코드스니펫] DetailPage.js
                                      • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { **Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); }** return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}**>팁 공유하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:100, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                                      • 공유하는 모습
                                    • <aside> 💡 share 함수를 만들고 거기에 react-native가 기본적으로 제공해주는 Share 함수를 사용하여 간단히 공유를 할 수 있습니다
                                    • 공유 모습
                                  • Linking이번엔 앱에서 외부 링크를 여는 방법에 대해 배워봅니다. 버튼을 누르면, 버튼에 연결 시킨 기능을 통해 외부 링크를 핸드폰에 있는 기본 브라우저로 열어봅니다.
                                  • </aside>
                                  • <aside> 👉 디테일 페이지에 있는 내용들이 사실 어디에서 가져온거라면? 출처를 남겨야 할겁니다. 그리고 그 출처로 바로가기 버튼정도도 있으면 여러분들이 만드는 다양한 플랫폼들을 서로 연결 시킬 수 있겠죠?
                                  • Linking 적용해보기
                                    • 준비!
                                      • DetailPage에 버튼을 다음과 같이 추가해주세요
                                      • 링크 버튼 추가 된 DetailPage 모습
                                      • expo 에서 제공해주는 도구를 설치 한다음, 해당 도구를 상단에 가져와 준비해야 합니다
                                      expo install expo-linking
                                      
                                      import * as Linking from 'expo-linking';
                                      
                                    • [실습 ✍️] 적용
                                      • [코드스니펫] DetailPage.js
                                      • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; import * as Linking from 'expo-linking'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); } const link = () => { Linking.openURL("<https://spartacodingclub.kr>") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}>팁 공유하기 link()}>외부 링크 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:90, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                                  07**. 3주차 끝 & 숙제 설명**
                                    1. 어바웃 화면 페이지화 시키고 버튼 추가하기!
                                    <aside> 👉 지난번 숙제로 만든 AboutPage.js를 아직 우린 스택 네비게이터에 연결하지 않았습니다.추가적으로 외부 링크 걸기까지 배웠으니, 여러분 인스타나 블로그 주소 혹은 아무주소를 AboutPage 하단 버튼에 연결해주세요!
                                    • 만들 화면
                                    • 가이드
                                      1. 메인에 TouchableOpacity 버튼을 두고 navigate를 연결시키면 이동이 가능하겠죠?
                                      2. Stack.screen에 AboutPage.js를 연결시켜야합니다!
                                      3. 소개 페이지 상태바의 디테일함을 주목하세요! 흰색이네요 🙂
                                  • </aside>
                                  • 따라서 여러분이 직접! 스택네비게이터에 AboutPage.js를 달아보구요! 하단 메인페이지의 상단에 다음과 같은 버튼을 만들어서 누르면 AboutPage로 가게끔 해주세요!
                                    1. 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요
                                  • 가이드
                                      1. 찜 데이터 제공
                                      <aside> 💡 나만의 꿀 팁 앱에서 찜 페이지란 쉽게 말해 팁 리스트 중에 선택한 팁들을 모아보는 페이지입니다. 이런 찜 페이지 자주 보셨죠?
                                      • 즉, 다음 상태값으로 화면을 구성해주세요::: 리스트에 딕셔너리 두 개가 들어있는 데이터입니다.
                                      </aside>
                                      • [코드스니펫] 찜 상태 데이터
                                      • const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }])
                                    • 하지만 아직 데이터를 저장하고, 수정하고 등등의 데이터 관리 부분을 배우지 않았기 때문에 [코드스니펫] 찜 상태 데이터를 가지고 화면만 그려볼겁니다.
                                      1. 파일의 위치와 이름
                                      <aside> 💡 새로운 페이지를 만드는 중이니, 어디에 위치해야 할까요? 당연히 pages 폴더에 위치시켜야 겠죠?</aside>
                                    • 또한 파일이름은 우리 LikePage.js 로 만들어 봅시다! ZzimPage는 좀...
                                      1. 메인의 꿀팁찜 버튼을 누르면 찜 페이지로!
                                      <aside> 💡 혼자만의 큰그림이 있었습니다. 메인페이지에 꿀팁찜 버튼이란걸 강의 초반부터 만들어 놓았었어요! 이 버튼을 누르면 찜 페이지로 가게끔 해봅시다!
                                    • </aside>
                                      1. Card.js 를 복붙해서 찜 페이지에서만 사용하는 LikeCard.js 만들기
                                      <aside> 💡 찜 목록도 Card.js와 크게 다를건 없습니다. 기존 카드에 찜 삭제 버튼정도가 추가 되었다는 점?이 다르므로, Card.js 코드를 복붙해서 LikeCard.js를 만들어 봅시다</aside>
                                    • 그런데! LikeCard.js에선 TouchableOpacity로 감싸는게 아닌! View 태그로 카드 전체를 감싸주도록 바꿔주세요!
                                      1. 최종 폴더 & 파일 모습
                                  HW. 3주차 숙제 해설
                                  • 숙제 1: 어바웃 화면 페이지화 시키고 버튼 추가하기!
                                    • [코드스니펫] StackNavigator.js(숙제1)
                                    • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> </Stack.Navigator> ) } export default StackNavigator;
                                    • [코드스니펫] AboutPage.js(숙제1)
                                    • import React,{useEffect} from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' import { StatusBar } from 'expo-status-bar'; export default function AboutPage({navigation,route}){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" useEffect(()=>{ navigation.setOptions({ title:"소개 페이지", headerStyle: { backgroundColor: '#1F266A', shadowColor: "#1F266A", }, headerTintColor: "#fff", }) },[]) return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:20, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
                                    • [코드스니펫] MainPage.js(숙제1)
                                    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20, }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
                                  • 숙제 2: 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요!
                                    • [코드스니펫] StackNavigator.js(숙제2)
                                    • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; import LikePage from '../pages/LikePage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> <Stack.Screen name="LikePage" component={LikePage}/> </Stack.Navigator> ) } export default StackNavigator;
                                    • [코드스니펫] LikeCard.js(숙제2)
                                    • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function LikeCard({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <View style={styles.card}> <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> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                                    • [코드스니펫] MainPage.js(숙제2)
                                    • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> **<TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>** </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
                                    • [코드스니펫] LikePage.js(숙제2)
                                    • import React,{useState, useEffect} from 'react'; import {ScrollView, Text, StyleSheet} from 'react-native'; import LikeCard from '../components/LikeCard'; import Card from '../components/Card'; export default function LikePage({navigation,route}){ const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }]) useEffect(()=>{ navigation.setOptions({ title:'꿀팁 찜' }) }) return ( { tip.map((content,i)=>{ return() }) } ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#fff" } })01. 오늘 배울 것
                                      • 오늘 배울 내용이 두 가지는 앞으로도 계속~ 반복 숙달합니다 🙂
                                        1. 리액트 필수 지식 활용
                                        2. Expo 기능 사용
                                        3. 페이지 적용
                                        </aside>
                                      • 오늘은 앱다운 앱을 만드는 연습과 기술을 배울텐데요! 다음 순서대로 지금까지 배웠던 것을 복습 하면서 앱다운 앱 기능을 배우니 점점 더 여러분은 앱 개발에 숙달되는 모습을 볼 수 있습니다!
                                      • <aside> 👉 지난 시간까지 자바스크립트 기초체력을 다지고 JSX로 화면을 그리는 방법 까지 배워봤습니다.
                                      • [앱 필수 기초지식] 리액트 필수지식
                                        • 컴포넌트(Component)
                                        • 상태(State,useState)
                                        • 속성(Props)
                                        • useEffect
                                        이렇게 네 가지입니다. 단어가 조금 어려워 보이지만, 재미있고 간단한 개념들입니다. 이 개념들을 통해 앱 코드를 체계적으로 관리할 수 있고 화면안에서 데이터를 자유자재로 관리할 수도 있습니다.리액트 네이티브(React Native)는 리액트(React.js) 기반으로 만들어진 앱 개발 기술이기 때문이에요! 겁먹지마시고 차근차근 다루어봅시다.
                                      • </aside>
                                      • 최소한의 리액트 개념을 배우는 이유는
                                      • <aside> 👉 리액트 네이티브 앱을 만들기 위해서 알아야 하는 최소한의 리액트 개념을 배웁니다.
                                      • [Expo 앱다운 앱기능] 앱다운 앱을 위해 사용할 리액트 네이티브와 Expo 기능들이렇게 앱 다운 앱이 되기위한 기능들을 공식문서를 보고 하나하나 적용해보는 시간을 갖습니다. 이 연습을 거치면 여러분들은 공식문서를 보며 필요한 앱 기능들을 여러분들 앱에 붙여나갈 수 있습니다!
                                      • </aside>
                                      • <aside> 👉 앱이라고 하면 보통 현재 보고 있는 내용을 친구들한테 공유하거나, 앱 안에 있는 링크를 누르면 바로 해당 링크로 화면이 전환되는 기능들이 들어 있습니다. 또한 배터리를 볼 수 있는 상태바 스타일도 앱 전체적인 스타일에 맞게 변경할 수도 있어야 할겁니다.
                                      • [앱 페이지 적용] 앱에 페이지 기능 넣기!그래서 앱안에 여러 페이지를 두고 버튼을 눌러 이동할 수 있는 기능을 달아봅니다. 본격적으로 거의 온전한 앱의 모습을 띄게 됩니다!
                                      • </aside>
                                      • <aside> 👉 앱에 페이지가 하나라면 너무 단조롭고 볼게 없는 앱이 되겠죠?
                                      02. [함께해보기] 나만의 꿀팁 앱 상세 화면
                                      • [실습 ✍️ ] 나만의 꿀팁 상세 페이지가될 아래의 화면을 만들어보세요
                                      • 퀴즈 가이드
                                        • [현재 코드 상황]
                                          • App.js
                                          • import React from 'react' import MainPage from './pages/MainPage'; import AboutPage from './pages/AboutPage'; export default function App(){ // return (<MainPage/>) return (<AboutPage/>) }
                                          • MainPage.js
                                          • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { 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> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, 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", } });
                                          • AboutPage.js
                                          • import React from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' export default function AboutPage(){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:100, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
                                          • 폴더구조
                                        • DetailPage.js를 pages 폴더에 만들어 넣기</aside>
                                        • <aside> 👉 저번 1주차 마무리 시간에, pages 폴더를 만들고 AboutPage.js 파일을 만들어 넣었던 기억이 나시나요? 그때랑 동일하게 pages 폴더안에 DetailPage.js 파일을 만들고 App.js에서 return 부분에 DetailPage.js를 넣어주세요 다음과 같이요!
                                        • 문제 디테일 화면을 위한 데이터딕셔너리 형태로 키값에 접근해서 값을 꺼내 사용해도 되고! 문자열들 그대로 복사해서 Text 태그에 넣어 사용해도 됩니다!
                                          • [코드스니펫] 상세 화면에서 사용될 데이터
                                          • const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }
                                        • </aside>
                                        • <aside> 👉 화면에 나타낼 꿀 팁은 다음 데이터를 사용하세요.
                                      • 함께 해보기</aside>
                                        • [코드스니펫] DetailPage.js 화면
                                        • import React from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage() { const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" } const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                                      • <aside> 👉 DetailPage를 확인하려면 App.js를 좀 바꿔야 겠죠? 어떻게 바꿔야 할까요?
                                      03**.** [앱 필수 기초지식] 컴포넌트, 속성, 상태, useEffect
                                      • 공부 할 리액트 필수 지식<aside> 👉 다시 떠올리자면, 리액트 네이티브는 리액트(React.js) 라이브러리 기반으로 만들어진 프레임워크 입니다. 그렇기 때문에 기본적인 구조는 리액트를 닯아 있습니다.1) 컴포넌트(Component) : 정해진 엘리먼트들(요소)을 사용하여 만든 화면의 일부분 2) 상태(State) : 컴포넌트에서 데이터를 유지하고 관리하기 위한 유일한 방법 == 그냥 사용할 데이터! 3) 속성(Props) : 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하는 방식 == 그냥 데이터 전달! 4) useEffect : 화면에 컴포넌트가 그려지면 처음 실행해야 하는 함수들을 모아두는 곳</aside>
                                      • 네 가지가 있습니다. 워워 어렵지 않아요. 이것도 정해진 규칙과 개념일 뿐! 사용방법 숙지!만 하면 그만이랍니다!
                                      • 대표적으로
                                      • 컴포넌트(Component)</aside><aside> 👉 컴포넌트는 즉, 화면의 모든 부분입니다. 그리고 컴포넌트란 App.js의 큰 App 함수처럼, 코드 전체를 감싸고 있는 함수를 뜻하기도 합니다.</aside>이렇게 컴포넌트로 생각하고 앱을 개발해 나아간다면, 코드 재사용이 용이 해집니다.실제 우리 예제에 어떻게 적용할 수 있는지. 확인해보겠습니다.
                                      • </aside>
                                      • 코드 재사용이란 어려운 용어는 아니고, 버튼 하나를 만들었을 때 이 버튼 코드를 여러 페이지에서 사용 가능하다는 뜻입니다.
                                      • <aside> 👉 버튼 하나가 컴포넌트가 될 수 있고, 버튼을 모아둔 영역이 컴포넌트가 될 수 있습니다.
                                      • 따라서 App.js를 App 컴포넌트라고도 부를 수 있습니다.
                                      • <aside> 👉 컴포넌트는 UI의 요소, 요소를 재사용 가능한 부분으로 조각내서 운영하는 기법입니다. 리액트 기반으로 만들어진 페이스북 웹사이트는 운영되는 컴포넌트가 수 만가지라고 합니다
                                      • [실습 ✍️ ] 메인화면 컴포넌트화 해보기그럼 다음 코드를 Card.js에 넣어주세요! 어디서 많이 본 코드입니다 그쵸?<aside> 🚧 마찬가지로 MainPage.js 화면을 확인하려면 App.js를 바꿔야 겠죠? return (<MainPage/>) 으로요!
                                        • [코드스니펫] Card.js
                                        • import React from "react" import {View,Text,Image,StyleSheet} from "react-native"; //비구조 할당 방식으로 넘긴 속성 데이터를 꺼내 사용함 export default function Card({content}) { return (<View style={styles.card}> <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>) } const styles = StyleSheet.create({ 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", } })
                                        <aside> 👉 맞습니다! MainPage.js에서 이미지위에 텍스트가 있던, 한 줄에 세 개씩 나열 되었던 그 카드 버튼입니다!.</aside>
                                        • [코드스니펫] MainPage.js
                                        • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; **import Card from '../components/Card';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (**<Card content={content} key={i}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                        <aside> 👉 MainPage.js에 있던 코드들을 Card.js로 옮겼습니다. 이렇게 옮기게 되면, 추후에 MainPage.js가 아닌 다른 페이지(페이지 또한 컴포넌트로 볼 수 있습니다) 또는 컴포넌트에서 Card.js를 사용할 수 있습니다.</aside>
                                        • MainPage.js
                                        • Card.js
                                        <aside> 👉 미리 말씀드리자면 MainPage.js에서 Card.js로 데이터를 넘기고 있는 모습입니다! 이를 속성을 넘긴다! 라고 보통 일컫는데 이는 다음 챕터에서 더 자세히 다루도록 하겠습니다!
                                      • </aside>
                                      • 그런데 약간 생소한 부분이 있습니다 다음 두 부분인데요!
                                      • 그럼 MainPage.js는 다음과 같이 변경해주세요
                                      • </aside>
                                      • </aside>
                                      • <aside> 👉 Components 폴더 안에 Card.js라는 파일을 만듭니다. 보통 컴포넌트들은 components 폴더에 모아두면서 관리하곤 합니다.
                                      • 속성(Props)<Text> 태그엔 numberOfLines이란 속성이 있었습니다. 말줌임표 효과를 주었죠! <Image> 태그엔 resizeMode란 속성이 있었습니다. 이미지가 영역을 차지하는 방식을 나타냈죠.</aside>예를 들어 Card를 사용하는 곳에서<Card image={'이미지 주소'}/> 라고 속성을 써 넣으면, 이 <Card/>는 image라는 속성이름에 {'이미지 주소'} 값을 을 갖게 됩니다. 즉 키와 벨류의 값을 갖게 됩니다 근데 이게 무슨 의미가 있을까요?</aside>
                                      • 의미가 있습니다. 속성을 부여받은 컴포넌트에서 해당 속성 값을 받아서 사용 할 수 있거든요! 다음과 같이 말이죠!
                                      • <aside> 👉 우리가 만든 <Card/> 컴포넌트에도 속성이 존재합니다. 우리가 부여 하면 부여하는 대로 속성이 되는 겁니다.
                                      • 이 모든 속성들은 공식 문서에 나와 있고, 해당 엘리먼트들이 태어날 때부터 가지고 있는 속성이였습니다.
                                      • <aside> 👉 속성은 쉽게 생각해서 컴포넌트에 데이터를 전달한다는 것이다. 그 전달 모습은 키와 벨류의 형태입니다.
                                      • [확인해보기👀 ]</aside>
                                        • MainPage.js 에서의 Card.js 컴포넌트
                                        <aside> 👉 어렵지 않은 규칙이 숨어 있습니다!
                                        1. 컴포넌트에 속성(데이터)을 부여해줘서 전달할땐, 키와 벨류(content={content}) 형태로 전달해줘야 할 것
                                        2. 컴포넌트를 반복문 돌릴땐, 컴포넌트마다 고유하다는 것을 표현하기 위해, map에서 나오는 인덱스(i)를 key = {i} 속성 전달 형태로 꼭 넣을것! 입니다.
                                        </aside>
                                        • Card.js 에서의 속성 값 내려 받기
                                        <aside> 👉 오랜만에 복습겸 나온 비구조 할당 방식 의 모습입니다. MainPage.js에서 넘겨준 속성은 실제 받게되는 컴포넌트에서 정말 딕셔너리 데이터를 받았다! 라고 생각하면 됩니다.이 방식으로 넘겨준 키값을 {키,키,키} 비구조 할당 방식으로 바로 꺼내서 사용하면 됩니다!
                                      • </aside>
                                      • 때문에, 비구조 할당 방식이 뭐였죠? 딕셔너리에서 키값을 바로 취해서 변수로써 함수안에서 즉시 사용할 수 있는 방식이였죠?
                                      • <aside> 👉 MainPage.js에서 방금전 컴포넌트화! 했던 Card.js를 우린 이렇게 사용했습니다.
                                      • 상태(State, useState)<aside> 👉 컴포넌트마다 데이터를 보유하고 관리 할 수 있습니다. 데이터라고 불러도 되지만, 리액트에서는 컴포넌트에서 보유/관리 되는 데이터를 **상태**라 부릅니다.사용방법은 실습을 통해 알아보도록 하겠습니다!
                                      • </aside>
                                      • 리액트에서 상태(state)는 리액트 라이브러리에서 제공해주는 useState로 생성하고 setState 함수로 정/변경 할 수 있습니다.
                                      • useState, 그렇다면 왜 중요할까?여러분이 만드는 화면은 데이터에 따라 변경됩니다 또 아무 데이터가 아니라 이 상태(state)로 관리되는 데이터가 변경되면 화면이 바뀝니다.</aside><aside> 👉 당시 개발자들끼리 이 상태와 컴포넌트의 관계를 공식으로 표현한 글을 보고 대단하다!! 했던 기억이 있는데, 여러분들은 어떻게 느끼실지 궁금하네요 ㅎㅎ무슨말인지 아직 잘 와닿지 않으시죠? 다른건 잘 와닿지 않아도 state는 컴포넌트에서 관리되는 상태만 기억하시면 충분합니다 이 개념은 이번 강의에서 개념을 다 배운 뒤, 다음 6 강에서 실습 하며 직접 확인할 수 있습니다
                                      • </aside>
                                      • 저 공식은 즉, 사용자 화면(UI)는 컴포넌트(component)에 어떤 데이터(state)가 주입되고 변경되냐에 따라 변화된다를 뜻합니다
                                      • UI = component(state)
                                      • 모르셔도 되지만 꽤 유의미한 공식이 있습니다
                                      • <aside> 👉 리액트는 특이한 점이 있습니다.
                                      • 화면이 그려진다음 가장 먼저 실행되는 함수, useEffect화면이 그려진 다음 가장 먼저 실행되는 함수로 사용방법이 간단합니다 형식만 알고 있으면 충분합니다
                                        **useEffect(()=>{**
                                        
                                        	...화면이 그려진 다음 가장 먼저 실행되야 할 코드 작성 공간
                                        
                                        **},[])**
                                        
                                        <aside> 👉 useEffect(()=>{ },[])</aside>
                                      • 이 안에서, 화면이 그려진다음 실행시키고 싶은 함수를 작성한다면 가장먼저 실행이 됩니다. useEffect 또한 어떤 역할을 하는지만 알고! 바로 실습을 통해 확인해보도록 하겠습니다
                                      • </aside>
                                      • <aside> 👉 useEffect는 너무 간단하고 유용하게 쓰이는 리액트 기본 제공 함수입니다.
                                      • useEffect, 그렇다면 왜 중요할까?
                                        1. 화면이 그려진다
                                        2. useEffect가 데이터를 준비한다
                                        3. 상태 데이터가 업데이트 되었으니 화면이 다시 그려진다
                                        화면이 그려진 다음, 서버에게 필요한 데이터를 요청하여 받은 후, 화면을 다시그릴때 주로 사용되는데요! 아주 일반적인 패턴이므로 여러분들도 금방 이해하고 익숙해지게 됩니다!
                                      • </aside>
                                      • <aside> 👉 보통 useEffect는 데이터를 준비할 때 사용합니다 데이터를 준비한다는 것은, 데이터를 서버로부터 혹은 어디선가로부터 받은 후 상태(state)에 반영한다는 것을 뜻합니다. 이런순서로 말이죠
                                      • [실습✍️ ] data.json을 상태(state)에 넣어서 관리해보기복습하실때 주석도 자세히 살펴보세요!
                                        • [코드스니펫] MainPage.js
                                        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //useState 사용법 //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수 //setState는 state를 변경시킬때 사용해야하는 함수 //모두 다 useState가 선물해줌 //useState()안에 전달되는 값은 state 초기값 const [state,setState] = useState([]) //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수 //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음 useEffect(()=>{ setState(data) },[]) // let tip = data.tip; //data.json 데이터는 state에 담기므로 상태에서 꺼내옴 let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                        <aside> 👉 그러나 실행을 하면 다음과 같은 오류 화면이 나옵니다
                                        • 오류화면
                                        <aside> 👉 그 이유는 이런 절차로 발생됩니다.
                                        1. 화면이 그려진다
                                        2. useEffect가 데이터를 state에 useState를 이용하여 업데이트한다
                                        3. 상태(state)가 변경되었으니 화면이 다시 그려진다
                                        여기서 1번에서 오류가 난겁니다. 화면이 가장 먼저 그려질때, 우리가 Card에 데이터를 넘기기위해 어떤일을 했나요?이런 오류는 우리가 지금 배운 개념들, 상태, 컴포넌트, useEffect를 이용하여 로딩화면을 만들어줌으로써 해결이 가능합니다!
                                      • </aside>
                                      • 처음부터 데이터가 없어서 , 꺼내올수 없는 오류
                                      • <aside> 👉 상태(state)에서 tip 키 값에 접근하여 데이터를 꺼내 map 즉, 반복문을 돌리려했습니다. 그런데 state에 데이터가 처음부터 있었나요? 없었죠? useEffect를 거쳐야 state에 데이터가 담기는데 아직 없는 상태라 오류가 나는 겁니다.
                                      • </aside>
                                      • </aside>
                                      • </aside>
                                      • <aside> 👉 지금 꿀팁을 data.json 파일에서 꺼내 바로 JSX 문법에서 사용하고 있습니다. 이를 다음과 같이 상태에 저장한 다음 사용하도록 변경해보도록 하겠습니다.
                                      04. [앱 필수 기초지식 응용] 로딩화면, 카테고리 기능, 상태 바
                                      • 앱 운영에 필요한 로딩화면이때 우린 로딩화면을 만들어줌으로써 쉽게 해결이 가능합니다. 컴포넌트 폴더에 Loading.js 파일을 만들어주고 다음 코드를 넣어주세요
                                        • [코드스니펫] Loading.js
                                        • import React from 'react'; import {View,Text,StyleSheet} from 'react-native'; export default function Loading(){ return(<View style={styles.container}><Text style={styles.title}>준비중입니다...</Text></View>) } const styles = StyleSheet.create({ container: { //앱의 배경 색 flex:1, justifyContent:'center', alignItems:'center', backgroundColor: '#fdc453', }, title: { fontSize:20, fontWeight:'700' } })
                                        <aside> 👉 그리고 MainPage.js 다음 코드 스니펫을 넣어주세요
                                        • [코드스니펫] MainPage.js
                                        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 const [state,setState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. **const [ready,setReady] = useState(true)** useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 **setTimeout(()=>{ setState(data) setReady(false) },1000)** },[]) // let tip = data.tip; let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" **//처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐)** return **ready ? <Loading/>** : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                        • 적용 모습
                                        <aside> 👉 ready 라는 새로운 상태값이 추가되었습니다. 즉, 상태값은 컴포넌트안에 여러개가 될 수 있습니다.즉, 이런 순서로 진행된거에요!
                                        1. ready 값이 true이므로 return 구문에서 ? 물음표 바로 뒤의 Loading 컴포넌트가 화면에 그려짐
                                        2. 화면이 그려지고 난다음, 1초 이따가 상태값들이 채워지고 변경됨
                                        3. ready 상태 값이 false가 됨
                                        4. 상태값이 변경되었으므로 화면이 다시 그려짐
                                        5. ready 값이 false 이므로 return 구문에서 : 콜론 뒤의 MainPage 컴포넌트가 화면에 그려짐
                                        차근차근 순서따라 반복해서 보면 별거아닙니다. 정해진 규칙에 따라 우린 코드를 적용했고 정해진 규칙에 따라 코드가 실행되고 있는 거에요!
                                      • </aside>
                                      • useEffect안에 setTimeout이라는 함수가 존재합니다. , 뒤에 있는 숫자 (1000 === 1초) 만큼, 지연됐다가 안에 있는 코드가 실행되는 지연 함수입니다.
                                      • </aside>
                                      • </aside>
                                      • <aside> 👉 5장에서 오류가 났던 것 처럼 화면이 그려질때 준비된 데이터가 없다!? 이러한 상황은 아주 빈번합니다.
                                      • 카테고리 버튼상태값이 바뀌면 화면이 다시 그려진다! 라는 개념을 염두하고 한번 만들어보겠습니다!
                                      • </aside>
                                      • <aside> 👉 우리에게 이런 버튼이 있었습니다. 카테고리 버튼인데요! 이 버튼은 장식이아니라, 실제 누르면 하단의 데이터가 카테고리에 맞게 다시 정렬되는 기능이 의도되어 있습니다.
                                      • [실습✍️ ] 카테고리 기능 넣기그리곤 함수가 필요합니다. 우리가 만든, 생활 재테크 반려견 버튼에 연결할 함수인데요!? 이 함수는 카테고리에 따라 카테고리 상태데이터를 새롭게 구성해주는 기능을 합니다.
                                        • [코드스니펫] MainPage.js
                                        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 **const [state,setState] = useState([])** //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 **const [cateState,setCateState] = useState([])** //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 **let tip = data.tip; setState(tip) setCateState(tip) setReady(false)** },1000) },[]) **const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } }** let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> **<TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>** <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, **middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 },** 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" }, **middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" },** cardContainer: { marginTop:10, marginLeft:10 }, });
                                        • 제태크를 눌렀을때의 모습
                                      • </aside>
                                      • <aside> 👉 카테고리 기능을 위해선 먼저 카테고리 상태가 필요합니다. 카테고리에 맞는 꿀팁들을 지속적으로 저장하고 관리할 상태 그릇이죠!
                                      • Expo에서 제공해주는 앱다운 앱 기능들</aside>
                                      • <aside> 💡 Expo SDK. 일명 Expo에서 제공해주는 앱 기능 도구들을 개발할 때 여기서 확인 할 수 있습니다. (링크) 필요한 기능들이 있는지 목록을 쭉 보고, 원하는 기능을 선택해서 적용하면 끝!
                                      • 상태 바란?</aside><aside> 💡 배터리가 충분한지, 몇시인지 매일 보시죠? 이부분도 우리가 처리를 할 수 있습니다.
                                      • </aside>
                                      • <aside> 💡 앱이 앱에 따라 모바일 맨 위 상태 바가 변하는 앱이 있습니다. 상태바란 이런 겁니다. 여러분들 매일 같이 보는 부분이에요!
                                      • StatusBar
                                        • 본격적으로 라이브러리 설치 시작!</aside><aside> 👉 터미널 우측 버튼중에 분할이란 버튼이 있습니다.</aside>
                                        • 여러분들 터미널 왼편은 서버를 켜고 끄고를 담당하고(expo start) 터미널 우측은 필요한 라이브러리들을 설치할 때 명령어를 치는 장소로 분할해서 사용하면 편리합니다!
                                        • <aside> 👉 이제 본격적으로 라이브러리들을 설치합니다. vscode에서 터미널을 이렇게 변경해주세요
                                        • [코드스니펫] Expo 상태 바 설치
                                        • expo install expo-status-bar
                                        • [실습 ✍️] 적용적용 모습을 살펴볼까요?
                                          • [코드스니펫] MainPage에 StatusBar 설치
                                          • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; **import { StatusBar } from 'expo-status-bar';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> **<StatusBar style="black" />** <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                          <aside> 💡 style이 light일 때와, black일 때가 다릅니다. 우린 앱 화면 배경색을 검은색으로 설정했기 떄문에, 상태바를 black으로 하면 보이지 않습니다.Status Bar 공식문서
                                          • black 일때
                                          • light 일때
                                        • </aside>
                                        • 상태 바 속성은 공식문서에 다양하게 존재하니, 살펴보면서 앱에 적합한 상태 바를 적용해보세요!
                                        • </aside>
                                        • <aside> 💡 컴포넌트마다 다르게 적용할 수도 있고, 앱 전체에 공통적으로 적용할 수 있습니다. StatusBar를 컴포넌트 각각 다르게 둘 수 있습니다 우린 MainPage.js에 달아볼거에요!
                                      05**. [앱** 페이지 적용**] 네비게이터 사용하기**
                                      • 네비게이션이란?<aside> 👉 앱에 페이지 개념을 입혀주고! 웹 사이트를 이용하듯, 앱에서 여러분들이 만든 컴포넌트들을 페이지화 시켜주고, 해당 페이지끼리 이동을 가능하게 해주는 라이브러리입니다가령 다음 화면과 같이, 앱 하단에 탭 버튼을 두고 해당 버튼을 누르면 연결되어 있는 페이지로 바로바로 이동할 수 있게 해주는 기능(열 앱에서 많이 보셨죠?)도 가지고 있습니다.</aside>
                                      • 따라서 이번시간에 같이 공부한 다음 틈틈히 다른 기능들도 적용해보세요!
                                      • 이번 강의에선 앱 내의 페이지 구성 및 이동까지 직접 적용해볼 텐데요!, 이 라이브러리는 다양한 기능들을 가지고 있습니다.
                                      • <aside> 💡 결국 이 라이브러리도 Expo에서 지원하고 있는 도구로써, 앱을 만들기 위해 사용 중인 Expo와 궁합이 아주 잘 맞습니다.
                                      • </aside>
                                      • <aside> 💡 물론 이것 또한 쉽게 페이징을 도와주는 외부 라이브러리를 가져다가 사용합니다. react-navigation 공식문서 보러가기 —> (링크)
                                      • </aside>
                                      • 현재 갖추고 있는 페이지 구성 확인!</aside>
                                      • <aside> 💡 페이지 구성은 보통 pages 폴더 안에 생성해둔 컴포넌트로 구성합니다. 우리는 이미, MainPage.js 메인 페이지 DetailPage.js 상세 화면 페이지 그리고 숙제로 만든 소개화면인 AboutPage.js 까지 총 3 페이지를 가지고 있습니다.
                                      • 기본 설치 코드우리에게 필요한 것이라고 했지만, 거의 대부분의 앱에 적용이 되어 있는 것들만 가져왔습니다.아래 명령어를 차례차례 터미널에 넣어 실행해주세요 가장 마지막줄은 꽤 깁니다. 이렇게 여러 라이브러리들을 띄어쓰기로 연결해서 한번에 설치 할 수도 있습니다.
                                        • [코드스니펫] 네비게이션 설치 코드
                                        • yarn add @react-navigation/native
                                        • [코드스니펫] 네비게이션 추가 설치코드
                                        • expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
                                      • </aside>
                                      • 아래 명령어들은 네비게이션을 사용하기 위해 필요한 기본 라이브러리들이구요! 우리가 곧 배울 스택 네비게이션와 탭 네비게이션 기능은 추가적으로 라이브러리를 또 설치 해줘야 합니다 😂
                                      • <aside> 💡 리액트 네이티브에서 페이지 네비게이션을 구현하기란 조금 까다롭습니다. 그래서 딱 우리에게 필요한 것만 가져와 설치 하고, 적용해 나갈 계획입니다.
                                      • 스택 네비게이션이란?</aside><aside> 💡 컴포넌트를 페이지화 시키는 스택 네비게이션은 다음과 같습니다.이렇게 만든 여러 페이지들을 책갈피 기능을 하는 스택 네비게이터에 모조리 등록시켜서, 언제든지 이 페이지 이동이 가능하게끔 해줍니다</aside>
                                      • 페이지는 Stack.Screen 이라 부르며 책갈피는 Stack.Navigator라 부릅니다
                                      • 우리가 페이지처럼 만든 컴포넌트를, 정말 페이지처럼 사용할 수 있게끔 페이지로 컴포넌트를 감싸 페이지로 만들어줍니다.
                                      • <aside> 💡 스택 네비게이션은 컴포넌트에 페이지 기능을 부여해주고 컴포넌트에서 컴포넌트로 이동, 즉 페이지 이동을 가능하게 해줍니다
                                      • createStackNavigator 사용해보기
                                        • [코드스니펫] 스택 네비게이터 설치 코드
                                        • yarn add @react-navigation/stack
                                        • [실습 ✍️] 적용하기</aside>
                                          • [코드스니펫] StackNavigator.js 스택 네비게이터
                                          • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
                                          <aside> 👉 워워 진정하세요! 위에서부터 차근차근 살펴볼거에요 🙂 언제나 우린 미니카 조립 설명서를 보면서 조립하듯, 공식 문서 내용 그대로 적용한다는 것 꼭 잊지마시고! 살펴보도록 하겠습니다.
                                          • 💻스택 네비게이터 코드 분석
                                            • 공식 문서 링크
                                            • 적용 순서 1) 사용 준비
                                            • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator();
                                            • 적용 순서 2) 기본 틀
                                            • //리액트의 모~든 파일은 컴포넌트라 생각하고 //페이지 기능을 해주는 모든 기능이 담겨 있는 컴포넌트를 만든다 생각하세요! const StackNavigator = () =>{ return ( /// 페이지 기능이 들어갈 곳 ) } export default StackNavigator;
                                            • 적용 순서 3) 스크린 옵션
                                            • //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
                                            • 적용 순서 4) 페이지 연결
                                            • {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/>
                                          <aside> 👉 컴포넌트를 페이지화 했고, 페이지를 이동할 수 있는 네이게이션도 준비가 됐다면, 우리는 최상단 컴포넌트 즉 App.js에 네비게이션 기능을 달아야 합니다.</aside>
                                          • [코드스니펫] App.js
                                          • import React from 'react'; //이제 모든 페이지 컴포넌트들이 끼워져있는 책갈피를 메인에 둘예정이므로 //컴포넌트를 더이상 불러오지 않아도 됩니다. // import MainPage from './pages/MainPage'; // import DetailPage from './pages/DetailPage'; import { StatusBar } from 'expo-status-bar'; //메인에 세팅할 네비게이션 도구들을 가져옵니다. import {NavigationContainer} from '@react-navigation/native'; import StackNavigator from './navigation/StackNavigator' export default function App() { console.disableYellowBox = true; return ( <NavigationContainer> <StatusBar style="black" /> <StackNavigator/> </NavigationContainer>); }
                                          • 스택 네비게이터 적용 후 MainPage 모습
                                        • 즉, 앱 가장 최상위 코드에 네비게이션을 다는겁니다 그래야 앱 어디서든 원하는 페이지 이동이 가능할테니까요!
                                        • </aside>
                                        • <aside> 👉 navigation 폴더 하나를 만들고 StackNavigator.js 파일을 만들어주세요 그리고 StackNavigator 안에 다음 코드를 넣어주세요
                                      • 페이지 헤더 수정</aside><aside> 💡 그 이유는 StackNavigator에서 현재 헤더에 대한 스타일과 페이지에 헤더의 제목까지 결정해주고 있기 때문입니다.
                                        • 스택 네비게이터의 헤더 스타일 부분 코드
                                        • <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTintColor: "#fff", headerBackTitleVisible: false }} > {/* name에 해당 하는 부분이 페이지의 타이틀이 됩니다.*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
                                        <aside> 💡 따라서 헤더 스타일을 적절히 바꾸기 위해 다음과 같이 옵션 부분의 코드를 수정해보도록 하겠습니다.
                                        • [코드스니펫] StackNavigator.js
                                        • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
                                        • [코드스니펫] MainPage.js 에서 title 삭제!
                                        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                        • StackNavigator 옵션 코드 수정 후 모습
                                        <aside> 💡 그래도 위에 페이지 제목으로 MainPage가 되는 모습은 뭔가 또 수정이 필요해보입니다. 이를 위해선 다음 스택 네비게이션 페이지 이동 강의에서 살펴보도록 하겠습니다
                                      • </aside>
                                      • </aside>
                                      • </aside>
                                      • <aside> 💡 그런데 현재 MainPage 화면은 다음과 같이 상단의 모습이 약간 이상해 보입니다.
                                      • 페이지 이동하기일단 페이지를 이동시키려면, 책갈피가 페이지들에게 부여해준 페이지 이동 기능을 사용해야 합니다.</aside><aside> 💡 일단 먼저 구조를 보여드렸습니다. 물론 사용을 해봐야 감이 오겠죠?같이 해보죠!
                                        • [실습 ✍️] 데이터 없이 페이지 이동하기
                                          • [코드스니펫] MainPage.js
                                          • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (**<Card content={content} key={i} navigation={navigation}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                          • [코드스니펫] Card.js
                                          • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 **export default function Card({content,navigation}){** return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage')}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                                        • navigation.navigate("DetailPage")
                                        • [실습 ✍️] 데이터 가지고 페이지 이동하기</aside><aside> 💡 그럼 Card에서 DetailPage로 이동할 때, MainPage로 부터 넘겨받은 content도 넘겨볼까요?</aside>
                                          • [코드스니펫] Card.js
                                          • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function Card({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage',**content**)}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                                          • [코드스니펫] DetailPage.js
                                          • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage({navigation,route}) { //초기 컴포넌트의 상태값을 설정 //state, setState 뿐 아니라 이름을 마음대로 지정할 수 있음! const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) //Card.js에서 navigation.navigate 함수를 쓸때 두번째 인자로 content를 넘겨줬죠? //content는 딕셔너리 그 자체였으므로 route.params에 고대~로 남겨옵니다. //즉, route.params 는 content죠! navigation.setOptions({ //setOptions로 페이지 타이틀도 지정 가능하고 title:route.params.title, //StackNavigator에서 작성했던 옵션을 다시 수정할 수도 있습니다. headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                                          • 이동한 디테일 페이지 모습
                                          <aside> 💡 건네 받은 값을 꺼낼 땐, 다음과 같이 책갈피가 navigation과 추가적으로 건네준 route에서 꺼내 확인 할 수 있습니다.</aside>
                                          • route에 담겨져 오는 데이터 콘솔에서 직접 확인
                                          • DetailPage에서 상태값을 초기에 설정한 이유</aside><aside> 💡 그 이유는 컴포넌트가 화면에 그려지는 순서에 있습니다. 이건 지금 단계에선 조금 과한감이 있어서 간략히만 설명해보면 다음과 같습니다.
                                            1. DetailPage 컴포넌트가 useState에 들어 있는 
                                               데이터 가지고 화면에 그려짐(return 함수실행)
                                            2. 화면에 다 그려진후, useEffect 함수 실행
                                            3. useEffect에서 상태값 변경 이벤트가 실행되면 변경된 데이터 가지고 다시 return 실행
                                            4. 변경된 데이터를 가지고 화면에 DetailPage가 다시 그려짐.
                                            
                                            <aside> 💡 곰곰히 생각해보면 결국, 리액트 네이티브에서 화면이 변경되는 시점은 컴포넌트의 상태값이 변경될 때니까요!또는! 우리가 한번 배웠떤 Loading.js를 이용하여 데이터가 준비가 되면 로딩 화면을 치우고, 본 화면을 보여준다던가!</aside>
                                          • 여러분들 마음껏 화면 처리를 해주시면 됩니다!
                                          • 이 개념은 어려울 수 있습니다 충분히! 따라서 처음에는, 어떠한 데이터를 보여주는 컴포넌트라면, 무조건 초기값을 의미없는 값이더라도 넣고 시작한다! 라고 생각하시면 편합니다!
                                          • </aside>
                                          • //초기 컴포넌트의 상태값을 설정 const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" })
                                          • <aside> 💡 DetailPage 초반에 우린 이렇게 상태값을 설정해놨었습니다. 그 이유가 뭘까요? 심지어 이 상태값을 초기에 설정안하면 오류가 발생합니다. tip엔 아무것도 없다며...
                                        • route.params 객체에 건네준 딕셔너리가 넘겨 있습니다!
                                        • 왜냐하면 지금은 어떠한 카드를 눌러도 상세 페이지에서 동일한 데이터를 보게 되잖아요? tip 데이터를 고정해놨으니까요!
                                        • navigation.navigate("Detail",**{** title**:** title **})**
                                        • <aside> 💡 버튼 카드에서 사용한 함수에 아래와 같이 두 번째 인자로 딕셔너리를 넘겨주면, 우리는 이동 한 페이지에서 넘겨준 데이터를 받을 수 있습니다.
                                      • </aside>
                                      • 그럼 이제 Card.js에 페이지 이동 기능을 달아보겠습니다. 코드를 실행 한 다음 페이지를 이동해 보면 익숙한 뒤로 가기 버튼도 보일거에요!
                                      • //navigation 객체가 가지고 있는 두 함수(setOptions와 navigate) //해당 페이지의 제목을 설정할 수 있음 navigation.setOptions({ title:'나만의 꿀팁' }) //Stack.screen에서 name 속성으로 정해준 이름을 지정해주면 해당 페이지로 이동하는 함수 navigation.navigate("DetailPage") //name 속성을 전달해주고, 두 번째 인자로 딕셔너리 데이터를 전달해주면, Detail 페이지에서 //두번째 인자로 전달된 딕셔너리 데이터를 route 딕셔너리로 로 받을 수 있음 navigation.navigate("DetailPage",**{ title: title }**) //전달받은 데이터를 받는 route 딕셔너리 //비구조 할당 방식으로 route에 params 객체 키로 연결되어 전달되는 데이터를 꺼내 사용 //navigate 함수로 전달되는 딕셔너리 데이터는 다음과 같은 모습이기 때문입니다. /* { route : { params :{ **title:title** } } } */ **const {** title**} = route.params;**
                                      • Stack.screen에 등록된 모든 페이지 컴포넌트들은 navigation 와 route 라는 딕셔너리(객체)를 속성으로 넘겨받아 사용할 수 있습니다. 이 두 딕셔너리는 다음과 같은 기능을 갖습니다.
                                      • <aside> 💡 Main 페이지(MainPage.js)에서 카드 버튼을 누르면(Card.js) 꿀팁 상세 페이지(DetailPage.js)로 이동합니다
                                      06**. [Expo 앱다운 앱기능] 공유 기능과 외부 링크 추가**
                                      • Share그런데 결과를 친구한테 공유하고 싶은 마음이 들수도 있겠네요 결과를 공유해주고, 앱다운로드 주소까지 전달해준다면 여러분이 만든 앱 홍보하기가 수월하겠죠?
                                      • </aside>
                                      • <aside> 👉 디테일 페이지까지 이동해서 도달했다면 여러분은 앱 개발자!
                                      • Share 적용해보기
                                        • 준비!
                                          • 설치 할 라이브러리 없이, react-native에서 기본적으로 제공해주는 공유 기능을 사용합니다.
                                          import { Share } from "react-native";
                                          
                                          • 그리고 우린 다음과 같은 공유 버튼을 디테일 페이지 하단에 생성하고 기능을 만들꺼에요! 한번 잠깐 영상을 멈추고 기능은 생각 말고 다음 버튼을 추가한 화면만 그려보세요! 복습 복습
                                          • 버튼 추가 디테일 페이지 모습
                                        • [실습 ✍️] 적용</aside>
                                          • [코드스니펫] DetailPage.js
                                          • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { **Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); }** return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}**>팁 공유하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:100, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                                          • 공유하는 모습
                                        • <aside> 💡 share 함수를 만들고 거기에 react-native가 기본적으로 제공해주는 Share 함수를 사용하여 간단히 공유를 할 수 있습니다
                                        • 공유 모습
                                      • Linking이번엔 앱에서 외부 링크를 여는 방법에 대해 배워봅니다. 버튼을 누르면, 버튼에 연결 시킨 기능을 통해 외부 링크를 핸드폰에 있는 기본 브라우저로 열어봅니다.
                                      • </aside>
                                      • <aside> 👉 디테일 페이지에 있는 내용들이 사실 어디에서 가져온거라면? 출처를 남겨야 할겁니다. 그리고 그 출처로 바로가기 버튼정도도 있으면 여러분들이 만드는 다양한 플랫폼들을 서로 연결 시킬 수 있겠죠?
                                      • Linking 적용해보기
                                        • 준비!
                                          • DetailPage에 버튼을 다음과 같이 추가해주세요
                                          • 링크 버튼 추가 된 DetailPage 모습
                                          • expo 에서 제공해주는 도구를 설치 한다음, 해당 도구를 상단에 가져와 준비해야 합니다
                                          expo install expo-linking
                                          
                                          import * as Linking from 'expo-linking';
                                          
                                        • [실습 ✍️] 적용
                                          • [코드스니펫] DetailPage.js
                                          • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; import * as Linking from 'expo-linking'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); } const link = () => { Linking.openURL("<https://spartacodingclub.kr>") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}>팁 공유하기 link()}>외부 링크 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:90, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                                      07**. 3주차 끝 & 숙제 설명**
                                        1. 어바웃 화면 페이지화 시키고 버튼 추가하기!
                                        <aside> 👉 지난번 숙제로 만든 AboutPage.js를 아직 우린 스택 네비게이터에 연결하지 않았습니다.추가적으로 외부 링크 걸기까지 배웠으니, 여러분 인스타나 블로그 주소 혹은 아무주소를 AboutPage 하단 버튼에 연결해주세요!
                                        • 만들 화면
                                        • 가이드
                                          1. 메인에 TouchableOpacity 버튼을 두고 navigate를 연결시키면 이동이 가능하겠죠?
                                          2. Stack.screen에 AboutPage.js를 연결시켜야합니다!
                                          3. 소개 페이지 상태바의 디테일함을 주목하세요! 흰색이네요 🙂
                                      • </aside>
                                      • 따라서 여러분이 직접! 스택네비게이터에 AboutPage.js를 달아보구요! 하단 메인페이지의 상단에 다음과 같은 버튼을 만들어서 누르면 AboutPage로 가게끔 해주세요!
                                        1. 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요
                                      • 가이드
                                          1. 찜 데이터 제공
                                          <aside> 💡 나만의 꿀 팁 앱에서 찜 페이지란 쉽게 말해 팁 리스트 중에 선택한 팁들을 모아보는 페이지입니다. 이런 찜 페이지 자주 보셨죠?
                                          • 즉, 다음 상태값으로 화면을 구성해주세요::: 리스트에 딕셔너리 두 개가 들어있는 데이터입니다.
                                          </aside>
                                          • [코드스니펫] 찜 상태 데이터
                                          • const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }])
                                        • 하지만 아직 데이터를 저장하고, 수정하고 등등의 데이터 관리 부분을 배우지 않았기 때문에 [코드스니펫] 찜 상태 데이터를 가지고 화면만 그려볼겁니다.
                                          1. 파일의 위치와 이름
                                          <aside> 💡 새로운 페이지를 만드는 중이니, 어디에 위치해야 할까요? 당연히 pages 폴더에 위치시켜야 겠죠?</aside>
                                        • 또한 파일이름은 우리 LikePage.js 로 만들어 봅시다! ZzimPage는 좀...
                                          1. 메인의 꿀팁찜 버튼을 누르면 찜 페이지로!
                                          <aside> 💡 혼자만의 큰그림이 있었습니다. 메인페이지에 꿀팁찜 버튼이란걸 강의 초반부터 만들어 놓았었어요! 이 버튼을 누르면 찜 페이지로 가게끔 해봅시다!
                                        • </aside>
                                          1. Card.js 를 복붙해서 찜 페이지에서만 사용하는 LikeCard.js 만들기
                                          <aside> 💡 찜 목록도 Card.js와 크게 다를건 없습니다. 기존 카드에 찜 삭제 버튼정도가 추가 되었다는 점?이 다르므로, Card.js 코드를 복붙해서 LikeCard.js를 만들어 봅시다</aside>
                                        • 그런데! LikeCard.js에선 TouchableOpacity로 감싸는게 아닌! View 태그로 카드 전체를 감싸주도록 바꿔주세요!
                                          1. 최종 폴더 & 파일 모습
                                      HW. 3주차 숙제 해설
                                      • 숙제 1: 어바웃 화면 페이지화 시키고 버튼 추가하기!
                                        • [코드스니펫] StackNavigator.js(숙제1)
                                        • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> </Stack.Navigator> ) } export default StackNavigator;
                                        • [코드스니펫] AboutPage.js(숙제1)
                                        • import React,{useEffect} from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' import { StatusBar } from 'expo-status-bar'; export default function AboutPage({navigation,route}){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" useEffect(()=>{ navigation.setOptions({ title:"소개 페이지", headerStyle: { backgroundColor: '#1F266A', shadowColor: "#1F266A", }, headerTintColor: "#fff", }) },[]) return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:20, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
                                        • [코드스니펫] MainPage.js(숙제1)
                                        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20, }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
                                      • 숙제 2: 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요!
                                        • [코드스니펫] StackNavigator.js(숙제2)
                                        • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; import LikePage from '../pages/LikePage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> <Stack.Screen name="LikePage" component={LikePage}/> </Stack.Navigator> ) } export default StackNavigator;
                                        • [코드스니펫] LikeCard.js(숙제2)
                                        • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function LikeCard({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <View style={styles.card}> <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> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                                        • [코드스니펫] MainPage.js(숙제2)
                                        • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> **<TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>** </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
                                        • [코드스니펫] LikePage.js(숙제2)
                                        • import React,{useState, useEffect} from 'react'; import {ScrollView, Text, StyleSheet} from 'react-native'; import LikeCard from '../components/LikeCard'; import Card from '../components/Card'; export default function LikePage({navigation,route}){ const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }]) useEffect(()=>{ navigation.setOptions({ title:'꿀팁 찜' }) }) return ( { tip.map((content,i)=>{ return() }) } ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#fff" } })01. 오늘 배울 것
                                          • 오늘 배울 내용이 두 가지는 앞으로도 계속~ 반복 숙달합니다 🙂
                                            1. 리액트 필수 지식 활용
                                            2. Expo 기능 사용
                                            3. 페이지 적용
                                            </aside>
                                          • 오늘은 앱다운 앱을 만드는 연습과 기술을 배울텐데요! 다음 순서대로 지금까지 배웠던 것을 복습 하면서 앱다운 앱 기능을 배우니 점점 더 여러분은 앱 개발에 숙달되는 모습을 볼 수 있습니다!
                                          • <aside> 👉 지난 시간까지 자바스크립트 기초체력을 다지고 JSX로 화면을 그리는 방법 까지 배워봤습니다.
                                          • [앱 필수 기초지식] 리액트 필수지식
                                            • 컴포넌트(Component)
                                            • 상태(State,useState)
                                            • 속성(Props)
                                            • useEffect
                                            이렇게 네 가지입니다. 단어가 조금 어려워 보이지만, 재미있고 간단한 개념들입니다. 이 개념들을 통해 앱 코드를 체계적으로 관리할 수 있고 화면안에서 데이터를 자유자재로 관리할 수도 있습니다.리액트 네이티브(React Native)는 리액트(React.js) 기반으로 만들어진 앱 개발 기술이기 때문이에요! 겁먹지마시고 차근차근 다루어봅시다.
                                          • </aside>
                                          • 최소한의 리액트 개념을 배우는 이유는
                                          • <aside> 👉 리액트 네이티브 앱을 만들기 위해서 알아야 하는 최소한의 리액트 개념을 배웁니다.
                                          • [Expo 앱다운 앱기능] 앱다운 앱을 위해 사용할 리액트 네이티브와 Expo 기능들이렇게 앱 다운 앱이 되기위한 기능들을 공식문서를 보고 하나하나 적용해보는 시간을 갖습니다. 이 연습을 거치면 여러분들은 공식문서를 보며 필요한 앱 기능들을 여러분들 앱에 붙여나갈 수 있습니다!
                                          • </aside>
                                          • <aside> 👉 앱이라고 하면 보통 현재 보고 있는 내용을 친구들한테 공유하거나, 앱 안에 있는 링크를 누르면 바로 해당 링크로 화면이 전환되는 기능들이 들어 있습니다. 또한 배터리를 볼 수 있는 상태바 스타일도 앱 전체적인 스타일에 맞게 변경할 수도 있어야 할겁니다.
                                          • [앱 페이지 적용] 앱에 페이지 기능 넣기!그래서 앱안에 여러 페이지를 두고 버튼을 눌러 이동할 수 있는 기능을 달아봅니다. 본격적으로 거의 온전한 앱의 모습을 띄게 됩니다!
                                          • </aside>
                                          • <aside> 👉 앱에 페이지가 하나라면 너무 단조롭고 볼게 없는 앱이 되겠죠?
                                          02. [함께해보기] 나만의 꿀팁 앱 상세 화면
                                          • [실습 ✍️ ] 나만의 꿀팁 상세 페이지가될 아래의 화면을 만들어보세요
                                          • 퀴즈 가이드
                                            • [현재 코드 상황]
                                              • App.js
                                              • import React from 'react' import MainPage from './pages/MainPage'; import AboutPage from './pages/AboutPage'; export default function App(){ // return (<MainPage/>) return (<AboutPage/>) }
                                              • MainPage.js
                                              • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { 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> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, 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", } });
                                              • AboutPage.js
                                              • import React from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' export default function AboutPage(){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:100, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
                                              • 폴더구조
                                            • DetailPage.js를 pages 폴더에 만들어 넣기</aside>
                                            • <aside> 👉 저번 1주차 마무리 시간에, pages 폴더를 만들고 AboutPage.js 파일을 만들어 넣었던 기억이 나시나요? 그때랑 동일하게 pages 폴더안에 DetailPage.js 파일을 만들고 App.js에서 return 부분에 DetailPage.js를 넣어주세요 다음과 같이요!
                                            • 문제 디테일 화면을 위한 데이터딕셔너리 형태로 키값에 접근해서 값을 꺼내 사용해도 되고! 문자열들 그대로 복사해서 Text 태그에 넣어 사용해도 됩니다!
                                              • [코드스니펫] 상세 화면에서 사용될 데이터
                                              • const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }
                                            • </aside>
                                            • <aside> 👉 화면에 나타낼 꿀 팁은 다음 데이터를 사용하세요.
                                          • 함께 해보기</aside>
                                            • [코드스니펫] DetailPage.js 화면
                                            • import React from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage() { const tip = { "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" } const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                                          • <aside> 👉 DetailPage를 확인하려면 App.js를 좀 바꿔야 겠죠? 어떻게 바꿔야 할까요?
                                          03**.** [앱 필수 기초지식] 컴포넌트, 속성, 상태, useEffect
                                          • 공부 할 리액트 필수 지식<aside> 👉 다시 떠올리자면, 리액트 네이티브는 리액트(React.js) 라이브러리 기반으로 만들어진 프레임워크 입니다. 그렇기 때문에 기본적인 구조는 리액트를 닯아 있습니다.1) 컴포넌트(Component) : 정해진 엘리먼트들(요소)을 사용하여 만든 화면의 일부분 2) 상태(State) : 컴포넌트에서 데이터를 유지하고 관리하기 위한 유일한 방법 == 그냥 사용할 데이터! 3) 속성(Props) : 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하는 방식 == 그냥 데이터 전달! 4) useEffect : 화면에 컴포넌트가 그려지면 처음 실행해야 하는 함수들을 모아두는 곳</aside>
                                          • 네 가지가 있습니다. 워워 어렵지 않아요. 이것도 정해진 규칙과 개념일 뿐! 사용방법 숙지!만 하면 그만이랍니다!
                                          • 대표적으로
                                          • 컴포넌트(Component)</aside><aside> 👉 컴포넌트는 즉, 화면의 모든 부분입니다. 그리고 컴포넌트란 App.js의 큰 App 함수처럼, 코드 전체를 감싸고 있는 함수를 뜻하기도 합니다.</aside>이렇게 컴포넌트로 생각하고 앱을 개발해 나아간다면, 코드 재사용이 용이 해집니다.실제 우리 예제에 어떻게 적용할 수 있는지. 확인해보겠습니다.
                                          • </aside>
                                          • 코드 재사용이란 어려운 용어는 아니고, 버튼 하나를 만들었을 때 이 버튼 코드를 여러 페이지에서 사용 가능하다는 뜻입니다.
                                          • <aside> 👉 버튼 하나가 컴포넌트가 될 수 있고, 버튼을 모아둔 영역이 컴포넌트가 될 수 있습니다.
                                          • 따라서 App.js를 App 컴포넌트라고도 부를 수 있습니다.
                                          • <aside> 👉 컴포넌트는 UI의 요소, 요소를 재사용 가능한 부분으로 조각내서 운영하는 기법입니다. 리액트 기반으로 만들어진 페이스북 웹사이트는 운영되는 컴포넌트가 수 만가지라고 합니다
                                          • [실습 ✍️ ] 메인화면 컴포넌트화 해보기그럼 다음 코드를 Card.js에 넣어주세요! 어디서 많이 본 코드입니다 그쵸?<aside> 🚧 마찬가지로 MainPage.js 화면을 확인하려면 App.js를 바꿔야 겠죠? return (<MainPage/>) 으로요!
                                            • [코드스니펫] Card.js
                                            • import React from "react" import {View,Text,Image,StyleSheet} from "react-native"; //비구조 할당 방식으로 넘긴 속성 데이터를 꺼내 사용함 export default function Card({content}) { return (<View style={styles.card}> <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>) } const styles = StyleSheet.create({ 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", } })
                                            <aside> 👉 맞습니다! MainPage.js에서 이미지위에 텍스트가 있던, 한 줄에 세 개씩 나열 되었던 그 카드 버튼입니다!.</aside>
                                            • [코드스니펫] MainPage.js
                                            • import React from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; **import Card from '../components/Card';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 let tip = data.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (**<Card content={content} key={i}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                            <aside> 👉 MainPage.js에 있던 코드들을 Card.js로 옮겼습니다. 이렇게 옮기게 되면, 추후에 MainPage.js가 아닌 다른 페이지(페이지 또한 컴포넌트로 볼 수 있습니다) 또는 컴포넌트에서 Card.js를 사용할 수 있습니다.</aside>
                                            • MainPage.js
                                            • Card.js
                                            <aside> 👉 미리 말씀드리자면 MainPage.js에서 Card.js로 데이터를 넘기고 있는 모습입니다! 이를 속성을 넘긴다! 라고 보통 일컫는데 이는 다음 챕터에서 더 자세히 다루도록 하겠습니다!
                                          • </aside>
                                          • 그런데 약간 생소한 부분이 있습니다 다음 두 부분인데요!
                                          • 그럼 MainPage.js는 다음과 같이 변경해주세요
                                          • </aside>
                                          • </aside>
                                          • <aside> 👉 Components 폴더 안에 Card.js라는 파일을 만듭니다. 보통 컴포넌트들은 components 폴더에 모아두면서 관리하곤 합니다.
                                          • 속성(Props)<Text> 태그엔 numberOfLines이란 속성이 있었습니다. 말줌임표 효과를 주었죠! <Image> 태그엔 resizeMode란 속성이 있었습니다. 이미지가 영역을 차지하는 방식을 나타냈죠.</aside>예를 들어 Card를 사용하는 곳에서<Card image={'이미지 주소'}/> 라고 속성을 써 넣으면, 이 <Card/>는 image라는 속성이름에 {'이미지 주소'} 값을 을 갖게 됩니다. 즉 키와 벨류의 값을 갖게 됩니다 근데 이게 무슨 의미가 있을까요?</aside>
                                          • 의미가 있습니다. 속성을 부여받은 컴포넌트에서 해당 속성 값을 받아서 사용 할 수 있거든요! 다음과 같이 말이죠!
                                          • <aside> 👉 우리가 만든 <Card/> 컴포넌트에도 속성이 존재합니다. 우리가 부여 하면 부여하는 대로 속성이 되는 겁니다.
                                          • 이 모든 속성들은 공식 문서에 나와 있고, 해당 엘리먼트들이 태어날 때부터 가지고 있는 속성이였습니다.
                                          • <aside> 👉 속성은 쉽게 생각해서 컴포넌트에 데이터를 전달한다는 것이다. 그 전달 모습은 키와 벨류의 형태입니다.
                                          • [확인해보기👀 ]</aside>
                                            • MainPage.js 에서의 Card.js 컴포넌트
                                            <aside> 👉 어렵지 않은 규칙이 숨어 있습니다!
                                            1. 컴포넌트에 속성(데이터)을 부여해줘서 전달할땐, 키와 벨류(content={content}) 형태로 전달해줘야 할 것
                                            2. 컴포넌트를 반복문 돌릴땐, 컴포넌트마다 고유하다는 것을 표현하기 위해, map에서 나오는 인덱스(i)를 key = {i} 속성 전달 형태로 꼭 넣을것! 입니다.
                                            </aside>
                                            • Card.js 에서의 속성 값 내려 받기
                                            <aside> 👉 오랜만에 복습겸 나온 비구조 할당 방식 의 모습입니다. MainPage.js에서 넘겨준 속성은 실제 받게되는 컴포넌트에서 정말 딕셔너리 데이터를 받았다! 라고 생각하면 됩니다.이 방식으로 넘겨준 키값을 {키,키,키} 비구조 할당 방식으로 바로 꺼내서 사용하면 됩니다!
                                          • </aside>
                                          • 때문에, 비구조 할당 방식이 뭐였죠? 딕셔너리에서 키값을 바로 취해서 변수로써 함수안에서 즉시 사용할 수 있는 방식이였죠?
                                          • <aside> 👉 MainPage.js에서 방금전 컴포넌트화! 했던 Card.js를 우린 이렇게 사용했습니다.
                                          • 상태(State, useState)<aside> 👉 컴포넌트마다 데이터를 보유하고 관리 할 수 있습니다. 데이터라고 불러도 되지만, 리액트에서는 컴포넌트에서 보유/관리 되는 데이터를 **상태**라 부릅니다.사용방법은 실습을 통해 알아보도록 하겠습니다!
                                          • </aside>
                                          • 리액트에서 상태(state)는 리액트 라이브러리에서 제공해주는 useState로 생성하고 setState 함수로 정/변경 할 수 있습니다.
                                          • useState, 그렇다면 왜 중요할까?여러분이 만드는 화면은 데이터에 따라 변경됩니다 또 아무 데이터가 아니라 이 상태(state)로 관리되는 데이터가 변경되면 화면이 바뀝니다.</aside><aside> 👉 당시 개발자들끼리 이 상태와 컴포넌트의 관계를 공식으로 표현한 글을 보고 대단하다!! 했던 기억이 있는데, 여러분들은 어떻게 느끼실지 궁금하네요 ㅎㅎ무슨말인지 아직 잘 와닿지 않으시죠? 다른건 잘 와닿지 않아도 state는 컴포넌트에서 관리되는 상태만 기억하시면 충분합니다 이 개념은 이번 강의에서 개념을 다 배운 뒤, 다음 6 강에서 실습 하며 직접 확인할 수 있습니다
                                          • </aside>
                                          • 저 공식은 즉, 사용자 화면(UI)는 컴포넌트(component)에 어떤 데이터(state)가 주입되고 변경되냐에 따라 변화된다를 뜻합니다
                                          • UI = component(state)
                                          • 모르셔도 되지만 꽤 유의미한 공식이 있습니다
                                          • <aside> 👉 리액트는 특이한 점이 있습니다.
                                          • 화면이 그려진다음 가장 먼저 실행되는 함수, useEffect화면이 그려진 다음 가장 먼저 실행되는 함수로 사용방법이 간단합니다 형식만 알고 있으면 충분합니다
                                            **useEffect(()=>{**
                                            
                                            	...화면이 그려진 다음 가장 먼저 실행되야 할 코드 작성 공간
                                            
                                            **},[])**
                                            
                                            <aside> 👉 useEffect(()=>{ },[])</aside>
                                          • 이 안에서, 화면이 그려진다음 실행시키고 싶은 함수를 작성한다면 가장먼저 실행이 됩니다. useEffect 또한 어떤 역할을 하는지만 알고! 바로 실습을 통해 확인해보도록 하겠습니다
                                          • </aside>
                                          • <aside> 👉 useEffect는 너무 간단하고 유용하게 쓰이는 리액트 기본 제공 함수입니다.
                                          • useEffect, 그렇다면 왜 중요할까?
                                            1. 화면이 그려진다
                                            2. useEffect가 데이터를 준비한다
                                            3. 상태 데이터가 업데이트 되었으니 화면이 다시 그려진다
                                            화면이 그려진 다음, 서버에게 필요한 데이터를 요청하여 받은 후, 화면을 다시그릴때 주로 사용되는데요! 아주 일반적인 패턴이므로 여러분들도 금방 이해하고 익숙해지게 됩니다!
                                          • </aside>
                                          • <aside> 👉 보통 useEffect는 데이터를 준비할 때 사용합니다 데이터를 준비한다는 것은, 데이터를 서버로부터 혹은 어디선가로부터 받은 후 상태(state)에 반영한다는 것을 뜻합니다. 이런순서로 말이죠
                                          • [실습✍️ ] data.json을 상태(state)에 넣어서 관리해보기복습하실때 주석도 자세히 살펴보세요!
                                            • [코드스니펫] MainPage.js
                                            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //useState 사용법 //[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수 //setState는 state를 변경시킬때 사용해야하는 함수 //모두 다 useState가 선물해줌 //useState()안에 전달되는 값은 state 초기값 const [state,setState] = useState([]) //하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수 //내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음 useEffect(()=>{ setState(data) },[]) // let tip = data.tip; //data.json 데이터는 state에 담기므로 상태에서 꺼내옴 let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" return ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                            <aside> 👉 그러나 실행을 하면 다음과 같은 오류 화면이 나옵니다
                                            • 오류화면
                                            <aside> 👉 그 이유는 이런 절차로 발생됩니다.
                                            1. 화면이 그려진다
                                            2. useEffect가 데이터를 state에 useState를 이용하여 업데이트한다
                                            3. 상태(state)가 변경되었으니 화면이 다시 그려진다
                                            여기서 1번에서 오류가 난겁니다. 화면이 가장 먼저 그려질때, 우리가 Card에 데이터를 넘기기위해 어떤일을 했나요?이런 오류는 우리가 지금 배운 개념들, 상태, 컴포넌트, useEffect를 이용하여 로딩화면을 만들어줌으로써 해결이 가능합니다!
                                          • </aside>
                                          • 처음부터 데이터가 없어서 , 꺼내올수 없는 오류
                                          • <aside> 👉 상태(state)에서 tip 키 값에 접근하여 데이터를 꺼내 map 즉, 반복문을 돌리려했습니다. 그런데 state에 데이터가 처음부터 있었나요? 없었죠? useEffect를 거쳐야 state에 데이터가 담기는데 아직 없는 상태라 오류가 나는 겁니다.
                                          • </aside>
                                          • </aside>
                                          • </aside>
                                          • <aside> 👉 지금 꿀팁을 data.json 파일에서 꺼내 바로 JSX 문법에서 사용하고 있습니다. 이를 다음과 같이 상태에 저장한 다음 사용하도록 변경해보도록 하겠습니다.
                                          04. [앱 필수 기초지식 응용] 로딩화면, 카테고리 기능, 상태 바
                                          • 앱 운영에 필요한 로딩화면이때 우린 로딩화면을 만들어줌으로써 쉽게 해결이 가능합니다. 컴포넌트 폴더에 Loading.js 파일을 만들어주고 다음 코드를 넣어주세요
                                            • [코드스니펫] Loading.js
                                            • import React from 'react'; import {View,Text,StyleSheet} from 'react-native'; export default function Loading(){ return(<View style={styles.container}><Text style={styles.title}>준비중입니다...</Text></View>) } const styles = StyleSheet.create({ container: { //앱의 배경 색 flex:1, justifyContent:'center', alignItems:'center', backgroundColor: '#fdc453', }, title: { fontSize:20, fontWeight:'700' } })
                                            <aside> 👉 그리고 MainPage.js 다음 코드 스니펫을 넣어주세요
                                            • [코드스니펫] MainPage.js
                                            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 const [state,setState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. **const [ready,setReady] = useState(true)** useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 **setTimeout(()=>{ setState(data) setReady(false) },1000)** },[]) // let tip = data.tip; let tip = state.tip; let todayWeather = 10 + 17; let todayCondition = "흐림" **//처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐)** return **ready ? <Loading/>** : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={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 */} { tip.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight: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 }, middleButtonText: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, middleButton04: { width:100, height:50, padding:15, backgroundColor:"#f886a8", borderRadius:15, margin:7 }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                            • 적용 모습
                                            <aside> 👉 ready 라는 새로운 상태값이 추가되었습니다. 즉, 상태값은 컴포넌트안에 여러개가 될 수 있습니다.즉, 이런 순서로 진행된거에요!
                                            1. ready 값이 true이므로 return 구문에서 ? 물음표 바로 뒤의 Loading 컴포넌트가 화면에 그려짐
                                            2. 화면이 그려지고 난다음, 1초 이따가 상태값들이 채워지고 변경됨
                                            3. ready 상태 값이 false가 됨
                                            4. 상태값이 변경되었으므로 화면이 다시 그려짐
                                            5. ready 값이 false 이므로 return 구문에서 : 콜론 뒤의 MainPage 컴포넌트가 화면에 그려짐
                                            차근차근 순서따라 반복해서 보면 별거아닙니다. 정해진 규칙에 따라 우린 코드를 적용했고 정해진 규칙에 따라 코드가 실행되고 있는 거에요!
                                          • </aside>
                                          • useEffect안에 setTimeout이라는 함수가 존재합니다. , 뒤에 있는 숫자 (1000 === 1초) 만큼, 지연됐다가 안에 있는 코드가 실행되는 지연 함수입니다.
                                          • </aside>
                                          • </aside>
                                          • <aside> 👉 5장에서 오류가 났던 것 처럼 화면이 그려질때 준비된 데이터가 없다!? 이러한 상황은 아주 빈번합니다.
                                          • 카테고리 버튼상태값이 바뀌면 화면이 다시 그려진다! 라는 개념을 염두하고 한번 만들어보겠습니다!
                                          • </aside>
                                          • <aside> 👉 우리에게 이런 버튼이 있었습니다. 카테고리 버튼인데요! 이 버튼은 장식이아니라, 실제 누르면 하단의 데이터가 카테고리에 맞게 다시 정렬되는 기능이 의도되어 있습니다.
                                          • [실습✍️ ] 카테고리 기능 넣기그리곤 함수가 필요합니다. 우리가 만든, 생활 재테크 반려견 버튼에 연결할 함수인데요!? 이 함수는 카테고리에 따라 카테고리 상태데이터를 새롭게 구성해주는 기능을 합니다.
                                            • [코드스니펫] MainPage.js
                                            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 **const [state,setState] = useState([])** //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 **const [cateState,setCateState] = useState([])** //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 **let tip = data.tip; setState(tip) setCateState(tip) setReady(false)** },1000) },[]) **const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } }** let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> **<TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity>** <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, **middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 },** 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" }, **middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" },** cardContainer: { marginTop:10, marginLeft:10 }, });
                                            • 제태크를 눌렀을때의 모습
                                          • </aside>
                                          • <aside> 👉 카테고리 기능을 위해선 먼저 카테고리 상태가 필요합니다. 카테고리에 맞는 꿀팁들을 지속적으로 저장하고 관리할 상태 그릇이죠!
                                          • Expo에서 제공해주는 앱다운 앱 기능들</aside>
                                          • <aside> 💡 Expo SDK. 일명 Expo에서 제공해주는 앱 기능 도구들을 개발할 때 여기서 확인 할 수 있습니다. (링크) 필요한 기능들이 있는지 목록을 쭉 보고, 원하는 기능을 선택해서 적용하면 끝!
                                          • 상태 바란?</aside><aside> 💡 배터리가 충분한지, 몇시인지 매일 보시죠? 이부분도 우리가 처리를 할 수 있습니다.
                                          • </aside>
                                          • <aside> 💡 앱이 앱에 따라 모바일 맨 위 상태 바가 변하는 앱이 있습니다. 상태바란 이런 겁니다. 여러분들 매일 같이 보는 부분이에요!
                                          • StatusBar
                                            • 본격적으로 라이브러리 설치 시작!</aside><aside> 👉 터미널 우측 버튼중에 분할이란 버튼이 있습니다.</aside>
                                            • 여러분들 터미널 왼편은 서버를 켜고 끄고를 담당하고(expo start) 터미널 우측은 필요한 라이브러리들을 설치할 때 명령어를 치는 장소로 분할해서 사용하면 편리합니다!
                                            • <aside> 👉 이제 본격적으로 라이브러리들을 설치합니다. vscode에서 터미널을 이렇게 변경해주세요
                                            • [코드스니펫] Expo 상태 바 설치
                                            • expo install expo-status-bar
                                            • [실습 ✍️] 적용적용 모습을 살펴볼까요?
                                              • [코드스니펫] MainPage에 StatusBar 설치
                                              • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; **import { StatusBar } from 'expo-status-bar';** export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> **<StatusBar style="black" />** <Text style={styles.title}>나만의 꿀팁</Text> <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                              <aside> 💡 style이 light일 때와, black일 때가 다릅니다. 우린 앱 화면 배경색을 검은색으로 설정했기 떄문에, 상태바를 black으로 하면 보이지 않습니다.Status Bar 공식문서
                                              • black 일때
                                              • light 일때
                                            • </aside>
                                            • 상태 바 속성은 공식문서에 다양하게 존재하니, 살펴보면서 앱에 적합한 상태 바를 적용해보세요!
                                            • </aside>
                                            • <aside> 💡 컴포넌트마다 다르게 적용할 수도 있고, 앱 전체에 공통적으로 적용할 수 있습니다. StatusBar를 컴포넌트 각각 다르게 둘 수 있습니다 우린 MainPage.js에 달아볼거에요!
                                          05**. [앱** 페이지 적용**] 네비게이터 사용하기**
                                          • 네비게이션이란?<aside> 👉 앱에 페이지 개념을 입혀주고! 웹 사이트를 이용하듯, 앱에서 여러분들이 만든 컴포넌트들을 페이지화 시켜주고, 해당 페이지끼리 이동을 가능하게 해주는 라이브러리입니다가령 다음 화면과 같이, 앱 하단에 탭 버튼을 두고 해당 버튼을 누르면 연결되어 있는 페이지로 바로바로 이동할 수 있게 해주는 기능(열 앱에서 많이 보셨죠?)도 가지고 있습니다.</aside>
                                          • 따라서 이번시간에 같이 공부한 다음 틈틈히 다른 기능들도 적용해보세요!
                                          • 이번 강의에선 앱 내의 페이지 구성 및 이동까지 직접 적용해볼 텐데요!, 이 라이브러리는 다양한 기능들을 가지고 있습니다.
                                          • <aside> 💡 결국 이 라이브러리도 Expo에서 지원하고 있는 도구로써, 앱을 만들기 위해 사용 중인 Expo와 궁합이 아주 잘 맞습니다.
                                          • </aside>
                                          • <aside> 💡 물론 이것 또한 쉽게 페이징을 도와주는 외부 라이브러리를 가져다가 사용합니다. react-navigation 공식문서 보러가기 —> (링크)
                                          • </aside>
                                          • 현재 갖추고 있는 페이지 구성 확인!</aside>
                                          • <aside> 💡 페이지 구성은 보통 pages 폴더 안에 생성해둔 컴포넌트로 구성합니다. 우리는 이미, MainPage.js 메인 페이지 DetailPage.js 상세 화면 페이지 그리고 숙제로 만든 소개화면인 AboutPage.js 까지 총 3 페이지를 가지고 있습니다.
                                          • 기본 설치 코드우리에게 필요한 것이라고 했지만, 거의 대부분의 앱에 적용이 되어 있는 것들만 가져왔습니다.아래 명령어를 차례차례 터미널에 넣어 실행해주세요 가장 마지막줄은 꽤 깁니다. 이렇게 여러 라이브러리들을 띄어쓰기로 연결해서 한번에 설치 할 수도 있습니다.
                                            • [코드스니펫] 네비게이션 설치 코드
                                            • yarn add @react-navigation/native
                                            • [코드스니펫] 네비게이션 추가 설치코드
                                            • expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
                                          • </aside>
                                          • 아래 명령어들은 네비게이션을 사용하기 위해 필요한 기본 라이브러리들이구요! 우리가 곧 배울 스택 네비게이션와 탭 네비게이션 기능은 추가적으로 라이브러리를 또 설치 해줘야 합니다 😂
                                          • <aside> 💡 리액트 네이티브에서 페이지 네비게이션을 구현하기란 조금 까다롭습니다. 그래서 딱 우리에게 필요한 것만 가져와 설치 하고, 적용해 나갈 계획입니다.
                                          • 스택 네비게이션이란?</aside><aside> 💡 컴포넌트를 페이지화 시키는 스택 네비게이션은 다음과 같습니다.이렇게 만든 여러 페이지들을 책갈피 기능을 하는 스택 네비게이터에 모조리 등록시켜서, 언제든지 이 페이지 이동이 가능하게끔 해줍니다</aside>
                                          • 페이지는 Stack.Screen 이라 부르며 책갈피는 Stack.Navigator라 부릅니다
                                          • 우리가 페이지처럼 만든 컴포넌트를, 정말 페이지처럼 사용할 수 있게끔 페이지로 컴포넌트를 감싸 페이지로 만들어줍니다.
                                          • <aside> 💡 스택 네비게이션은 컴포넌트에 페이지 기능을 부여해주고 컴포넌트에서 컴포넌트로 이동, 즉 페이지 이동을 가능하게 해줍니다
                                          • createStackNavigator 사용해보기
                                            • [코드스니펫] 스택 네비게이터 설치 코드
                                            • yarn add @react-navigation/stack
                                            • [실습 ✍️] 적용하기</aside>
                                              • [코드스니펫] StackNavigator.js 스택 네비게이터
                                              • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
                                              <aside> 👉 워워 진정하세요! 위에서부터 차근차근 살펴볼거에요 🙂 언제나 우린 미니카 조립 설명서를 보면서 조립하듯, 공식 문서 내용 그대로 적용한다는 것 꼭 잊지마시고! 살펴보도록 하겠습니다.
                                              • 💻스택 네비게이터 코드 분석
                                                • 공식 문서 링크
                                                • 적용 순서 1) 사용 준비
                                                • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator();
                                                • 적용 순서 2) 기본 틀
                                                • //리액트의 모~든 파일은 컴포넌트라 생각하고 //페이지 기능을 해주는 모든 기능이 담겨 있는 컴포넌트를 만든다 생각하세요! const StackNavigator = () =>{ return ( /// 페이지 기능이 들어갈 곳 ) } export default StackNavigator;
                                                • 적용 순서 3) 스크린 옵션
                                                • //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, headerTintColor: "#FFFFFF", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
                                                • 적용 순서 4) 페이지 연결
                                                • {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/>
                                              <aside> 👉 컴포넌트를 페이지화 했고, 페이지를 이동할 수 있는 네이게이션도 준비가 됐다면, 우리는 최상단 컴포넌트 즉 App.js에 네비게이션 기능을 달아야 합니다.</aside>
                                              • [코드스니펫] App.js
                                              • import React from 'react'; //이제 모든 페이지 컴포넌트들이 끼워져있는 책갈피를 메인에 둘예정이므로 //컴포넌트를 더이상 불러오지 않아도 됩니다. // import MainPage from './pages/MainPage'; // import DetailPage from './pages/DetailPage'; import { StatusBar } from 'expo-status-bar'; //메인에 세팅할 네비게이션 도구들을 가져옵니다. import {NavigationContainer} from '@react-navigation/native'; import StackNavigator from './navigation/StackNavigator' export default function App() { console.disableYellowBox = true; return ( <NavigationContainer> <StatusBar style="black" /> <StackNavigator/> </NavigationContainer>); }
                                              • 스택 네비게이터 적용 후 MainPage 모습
                                            • 즉, 앱 가장 최상위 코드에 네비게이션을 다는겁니다 그래야 앱 어디서든 원하는 페이지 이동이 가능할테니까요!
                                            • </aside>
                                            • <aside> 👉 navigation 폴더 하나를 만들고 StackNavigator.js 파일을 만들어주세요 그리고 StackNavigator 안에 다음 코드를 넣어주세요
                                          • 페이지 헤더 수정</aside><aside> 💡 그 이유는 StackNavigator에서 현재 헤더에 대한 스타일과 페이지에 헤더의 제목까지 결정해주고 있기 때문입니다.
                                            • 스택 네비게이터의 헤더 스타일 부분 코드
                                            • <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "black", borderBottomColor: "black", shadowColor: "black", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTintColor: "#fff", headerBackTitleVisible: false }} > {/* name에 해당 하는 부분이 페이지의 타이틀이 됩니다.*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator>
                                            <aside> 💡 따라서 헤더 스타일을 적절히 바꾸기 위해 다음과 같이 옵션 부분의 코드를 수정해보도록 하겠습니다.
                                            • [코드스니펫] StackNavigator.js
                                            • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> </Stack.Navigator> ) } export default StackNavigator;
                                            • [코드스니펫] MainPage.js 에서 title 삭제!
                                            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage() { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                            • StackNavigator 옵션 코드 수정 후 모습
                                            <aside> 💡 그래도 위에 페이지 제목으로 MainPage가 되는 모습은 뭔가 또 수정이 필요해보입니다. 이를 위해선 다음 스택 네비게이션 페이지 이동 강의에서 살펴보도록 하겠습니다
                                          • </aside>
                                          • </aside>
                                          • </aside>
                                          • <aside> 💡 그런데 현재 MainPage 화면은 다음과 같이 상단의 모습이 약간 이상해 보입니다.
                                          • 페이지 이동하기일단 페이지를 이동시키려면, 책갈피가 페이지들에게 부여해준 페이지 이동 기능을 사용해야 합니다.</aside><aside> 💡 일단 먼저 구조를 보여드렸습니다. 물론 사용을 해봐야 감이 오겠죠?같이 해보죠!
                                            • [실습 ✍️] 데이터 없이 페이지 이동하기
                                              • [코드스니펫] MainPage.js
                                              • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (**<Card content={content} key={i} navigation={navigation}/>**) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, });
                                              • [코드스니펫] Card.js
                                              • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 **export default function Card({content,navigation}){** return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage')}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                                            • navigation.navigate("DetailPage")
                                            • [실습 ✍️] 데이터 가지고 페이지 이동하기</aside><aside> 💡 그럼 Card에서 DetailPage로 이동할 때, MainPage로 부터 넘겨받은 content도 넘겨볼까요?</aside>
                                              • [코드스니펫] Card.js
                                              • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function Card({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage',**content**)}}> <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> </TouchableOpacity> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                                              • [코드스니펫] DetailPage.js
                                              • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert } from 'react-native'; export default function DetailPage({navigation,route}) { //초기 컴포넌트의 상태값을 설정 //state, setState 뿐 아니라 이름을 마음대로 지정할 수 있음! const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) //Card.js에서 navigation.navigate 함수를 쓸때 두번째 인자로 content를 넘겨줬죠? //content는 딕셔너리 그 자체였으므로 route.params에 고대~로 남겨옵니다. //즉, route.params 는 content죠! navigation.setOptions({ //setOptions로 페이지 타이틀도 지정 가능하고 title:route.params.title, //StackNavigator에서 작성했던 옵션을 다시 수정할 수도 있습니다. headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, button:{ width:100, marginTop:20, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                                              • 이동한 디테일 페이지 모습
                                              <aside> 💡 건네 받은 값을 꺼낼 땐, 다음과 같이 책갈피가 navigation과 추가적으로 건네준 route에서 꺼내 확인 할 수 있습니다.</aside>
                                              • route에 담겨져 오는 데이터 콘솔에서 직접 확인
                                              • DetailPage에서 상태값을 초기에 설정한 이유</aside><aside> 💡 그 이유는 컴포넌트가 화면에 그려지는 순서에 있습니다. 이건 지금 단계에선 조금 과한감이 있어서 간략히만 설명해보면 다음과 같습니다.
                                                1. DetailPage 컴포넌트가 useState에 들어 있는 
                                                   데이터 가지고 화면에 그려짐(return 함수실행)
                                                2. 화면에 다 그려진후, useEffect 함수 실행
                                                3. useEffect에서 상태값 변경 이벤트가 실행되면 변경된 데이터 가지고 다시 return 실행
                                                4. 변경된 데이터를 가지고 화면에 DetailPage가 다시 그려짐.
                                                
                                                <aside> 💡 곰곰히 생각해보면 결국, 리액트 네이티브에서 화면이 변경되는 시점은 컴포넌트의 상태값이 변경될 때니까요!또는! 우리가 한번 배웠떤 Loading.js를 이용하여 데이터가 준비가 되면 로딩 화면을 치우고, 본 화면을 보여준다던가!</aside>
                                              • 여러분들 마음껏 화면 처리를 해주시면 됩니다!
                                              • 이 개념은 어려울 수 있습니다 충분히! 따라서 처음에는, 어떠한 데이터를 보여주는 컴포넌트라면, 무조건 초기값을 의미없는 값이더라도 넣고 시작한다! 라고 생각하시면 편합니다!
                                              • </aside>
                                              • //초기 컴포넌트의 상태값을 설정 const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" })
                                              • <aside> 💡 DetailPage 초반에 우린 이렇게 상태값을 설정해놨었습니다. 그 이유가 뭘까요? 심지어 이 상태값을 초기에 설정안하면 오류가 발생합니다. tip엔 아무것도 없다며...
                                            • route.params 객체에 건네준 딕셔너리가 넘겨 있습니다!
                                            • 왜냐하면 지금은 어떠한 카드를 눌러도 상세 페이지에서 동일한 데이터를 보게 되잖아요? tip 데이터를 고정해놨으니까요!
                                            • navigation.navigate("Detail",**{** title**:** title **})**
                                            • <aside> 💡 버튼 카드에서 사용한 함수에 아래와 같이 두 번째 인자로 딕셔너리를 넘겨주면, 우리는 이동 한 페이지에서 넘겨준 데이터를 받을 수 있습니다.
                                          • </aside>
                                          • 그럼 이제 Card.js에 페이지 이동 기능을 달아보겠습니다. 코드를 실행 한 다음 페이지를 이동해 보면 익숙한 뒤로 가기 버튼도 보일거에요!
                                          • //navigation 객체가 가지고 있는 두 함수(setOptions와 navigate) //해당 페이지의 제목을 설정할 수 있음 navigation.setOptions({ title:'나만의 꿀팁' }) //Stack.screen에서 name 속성으로 정해준 이름을 지정해주면 해당 페이지로 이동하는 함수 navigation.navigate("DetailPage") //name 속성을 전달해주고, 두 번째 인자로 딕셔너리 데이터를 전달해주면, Detail 페이지에서 //두번째 인자로 전달된 딕셔너리 데이터를 route 딕셔너리로 로 받을 수 있음 navigation.navigate("DetailPage",**{ title: title }**) //전달받은 데이터를 받는 route 딕셔너리 //비구조 할당 방식으로 route에 params 객체 키로 연결되어 전달되는 데이터를 꺼내 사용 //navigate 함수로 전달되는 딕셔너리 데이터는 다음과 같은 모습이기 때문입니다. /* { route : { params :{ **title:title** } } } */ **const {** title**} = route.params;**
                                          • Stack.screen에 등록된 모든 페이지 컴포넌트들은 navigation 와 route 라는 딕셔너리(객체)를 속성으로 넘겨받아 사용할 수 있습니다. 이 두 딕셔너리는 다음과 같은 기능을 갖습니다.
                                          • <aside> 💡 Main 페이지(MainPage.js)에서 카드 버튼을 누르면(Card.js) 꿀팁 상세 페이지(DetailPage.js)로 이동합니다
                                          06**. [Expo 앱다운 앱기능] 공유 기능과 외부 링크 추가**
                                          • Share그런데 결과를 친구한테 공유하고 싶은 마음이 들수도 있겠네요 결과를 공유해주고, 앱다운로드 주소까지 전달해준다면 여러분이 만든 앱 홍보하기가 수월하겠죠?
                                          • </aside>
                                          • <aside> 👉 디테일 페이지까지 이동해서 도달했다면 여러분은 앱 개발자!
                                          • Share 적용해보기
                                            • 준비!
                                              • 설치 할 라이브러리 없이, react-native에서 기본적으로 제공해주는 공유 기능을 사용합니다.
                                              import { Share } from "react-native";
                                              
                                              • 그리고 우린 다음과 같은 공유 버튼을 디테일 페이지 하단에 생성하고 기능을 만들꺼에요! 한번 잠깐 영상을 멈추고 기능은 생각 말고 다음 버튼을 추가한 화면만 그려보세요! 복습 복습
                                              • 버튼 추가 디테일 페이지 모습
                                            • [실습 ✍️] 적용</aside>
                                              • [코드스니펫] DetailPage.js
                                              • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { **Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); }** return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}**>팁 공유하기 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:100, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                                              • 공유하는 모습
                                            • <aside> 💡 share 함수를 만들고 거기에 react-native가 기본적으로 제공해주는 Share 함수를 사용하여 간단히 공유를 할 수 있습니다
                                            • 공유 모습
                                          • Linking이번엔 앱에서 외부 링크를 여는 방법에 대해 배워봅니다. 버튼을 누르면, 버튼에 연결 시킨 기능을 통해 외부 링크를 핸드폰에 있는 기본 브라우저로 열어봅니다.
                                          • </aside>
                                          • <aside> 👉 디테일 페이지에 있는 내용들이 사실 어디에서 가져온거라면? 출처를 남겨야 할겁니다. 그리고 그 출처로 바로가기 버튼정도도 있으면 여러분들이 만드는 다양한 플랫폼들을 서로 연결 시킬 수 있겠죠?
                                          • Linking 적용해보기
                                            • 준비!
                                              • DetailPage에 버튼을 다음과 같이 추가해주세요
                                              • 링크 버튼 추가 된 DetailPage 모습
                                              • expo 에서 제공해주는 도구를 설치 한다음, 해당 도구를 상단에 가져와 준비해야 합니다
                                              expo install expo-linking
                                              
                                              import * as Linking from 'expo-linking';
                                              
                                            • [실습 ✍️] 적용
                                              • [코드스니펫] DetailPage.js
                                              • import React,{useState,useEffect} from 'react'; import { StyleSheet, Text, View, Image, ScrollView,TouchableOpacity,Alert,Share } from 'react-native'; import * as Linking from 'expo-linking'; export default function DetailPage({navigation,route}) { const [tip, setTip] = useState({ "idx":9, "category":"재테크", "title":"렌탈 서비스 금액 비교해보기", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Frental.png?alt=media&token=97a55844-f077-4aeb-8402-e0a27221570b>", "desc":"요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. ", "date":"2020.09.09" }) useEffect(()=>{ console.log(route) navigation.setOptions({ title:route.params.title, headerStyle: { backgroundColor: '#000', shadowColor: "#000", }, headerTintColor: "#fff", }) setTip(route.params) },[]) const popup = () => { Alert.alert("팝업!!") } const share = () => { Share.share({ message:`${tip.title} \\n\\n ${tip.desc} \\n\\n ${tip.image}`, }); } const link = () => { Linking.openURL("<https://spartacodingclub.kr>") } return ( // ScrollView에서의 flex 숫자는 의미가 없습니다. 정확히 보여지는 화면을 몇등분 하지 않고 // 화면에 넣은 컨텐츠를 모두 보여주려 스크롤 기능이 존재하기 때문입니다. // 여기선 내부의 컨텐츠들 영역을 결정짓기 위해서 height 값과 margin,padding 값을 적절히 잘 이용해야 합니다. {tip.title} {tip.desc} popup()}>팁 찜하기 share()}>팁 공유하기 link()}>외부 링크 ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#000" }, image:{ height:400, margin:10, marginTop:40, borderRadius:20 }, textContainer:{ padding:20, justifyContent:'center', alignItems:'center' }, title: { fontSize:20, fontWeight:'700', color:"#eee" }, desc:{ marginTop:10, color:"#eee" }, buttonGroup: { flexDirection:"row", }, button:{ width:90, marginTop:20, marginRight:10, marginLeft:10, padding:10, borderWidth:1, borderColor:'deeppink', borderRadius:7 }, buttonText:{ color:'#fff', textAlign:'center' } })
                                          07**. 3주차 끝 & 숙제 설명**
                                            1. 어바웃 화면 페이지화 시키고 버튼 추가하기!
                                            <aside> 👉 지난번 숙제로 만든 AboutPage.js를 아직 우린 스택 네비게이터에 연결하지 않았습니다.추가적으로 외부 링크 걸기까지 배웠으니, 여러분 인스타나 블로그 주소 혹은 아무주소를 AboutPage 하단 버튼에 연결해주세요!
                                            • 만들 화면
                                            • 가이드
                                              1. 메인에 TouchableOpacity 버튼을 두고 navigate를 연결시키면 이동이 가능하겠죠?
                                              2. Stack.screen에 AboutPage.js를 연결시켜야합니다!
                                              3. 소개 페이지 상태바의 디테일함을 주목하세요! 흰색이네요 🙂
                                          • </aside>
                                          • 따라서 여러분이 직접! 스택네비게이터에 AboutPage.js를 달아보구요! 하단 메인페이지의 상단에 다음과 같은 버튼을 만들어서 누르면 AboutPage로 가게끔 해주세요!
                                            1. 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요
                                          • 가이드
                                              1. 찜 데이터 제공
                                              <aside> 💡 나만의 꿀 팁 앱에서 찜 페이지란 쉽게 말해 팁 리스트 중에 선택한 팁들을 모아보는 페이지입니다. 이런 찜 페이지 자주 보셨죠?
                                              • 즉, 다음 상태값으로 화면을 구성해주세요::: 리스트에 딕셔너리 두 개가 들어있는 데이터입니다.
                                              </aside>
                                              • [코드스니펫] 찜 상태 데이터
                                              • const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }])
                                            • 하지만 아직 데이터를 저장하고, 수정하고 등등의 데이터 관리 부분을 배우지 않았기 때문에 [코드스니펫] 찜 상태 데이터를 가지고 화면만 그려볼겁니다.
                                              1. 파일의 위치와 이름
                                              <aside> 💡 새로운 페이지를 만드는 중이니, 어디에 위치해야 할까요? 당연히 pages 폴더에 위치시켜야 겠죠?</aside>
                                            • 또한 파일이름은 우리 LikePage.js 로 만들어 봅시다! ZzimPage는 좀...
                                              1. 메인의 꿀팁찜 버튼을 누르면 찜 페이지로!
                                              <aside> 💡 혼자만의 큰그림이 있었습니다. 메인페이지에 꿀팁찜 버튼이란걸 강의 초반부터 만들어 놓았었어요! 이 버튼을 누르면 찜 페이지로 가게끔 해봅시다!
                                            • </aside>
                                              1. Card.js 를 복붙해서 찜 페이지에서만 사용하는 LikeCard.js 만들기
                                              <aside> 💡 찜 목록도 Card.js와 크게 다를건 없습니다. 기존 카드에 찜 삭제 버튼정도가 추가 되었다는 점?이 다르므로, Card.js 코드를 복붙해서 LikeCard.js를 만들어 봅시다</aside>
                                            • 그런데! LikeCard.js에선 TouchableOpacity로 감싸는게 아닌! View 태그로 카드 전체를 감싸주도록 바꿔주세요!
                                              1. 최종 폴더 & 파일 모습
                                          HW. 3주차 숙제 해설
                                          • 숙제 1: 어바웃 화면 페이지화 시키고 버튼 추가하기!
                                            • [코드스니펫] StackNavigator.js(숙제1)
                                            • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> </Stack.Navigator> ) } export default StackNavigator;
                                            • [코드스니펫] AboutPage.js(숙제1)
                                            • import React,{useEffect} from 'react' import {View,Text,StyleSheet,Image, TouchableOpacity} from 'react-native' import { StatusBar } from 'expo-status-bar'; export default function AboutPage({navigation,route}){ const aboutImage = "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2FaboutImage.png?alt=media&token=13e1c4f6-b802-4975-9773-e305fc7475c4>" useEffect(()=>{ navigation.setOptions({ title:"소개 페이지", headerStyle: { backgroundColor: '#1F266A', shadowColor: "#1F266A", }, headerTintColor: "#fff", }) },[]) return ( HI! 스파르타코딩 앱개발 반에 오신것을 환영합니다 많은 내용을 간결하게 담아내려 노력했습니다! 꼭 완주 하셔서 꼭 여러분것으로 만들어가시길 바랍니다 여러분의 인스타계정 ) } const styles = StyleSheet.create({ container: { flex:1, backgroundColor:"#1F266A", alignItems:"center" }, title: { fontSize:30, fontWeight:"700", color:"#fff", paddingLeft:30, paddingTop:20, paddingRight:30 }, textContainer: { width:300, height:500, backgroundColor:"#fff", marginTop:50, borderRadius:30, justifyContent:"center", alignItems:"center" }, aboutImage:{ width:150, height:150, borderRadius:30 }, desc01: { textAlign:"center", fontSize:20, fontWeight:"700", paddingLeft:22, paddingRight:22 }, desc02: { textAlign:"center", fontSize:15, fontWeight:"700", padding:22 }, button:{ backgroundColor:"orange", padding:20, borderRadius:15 }, buttonText: { color:"#fff", fontSize:15, fontWeight:"700" } })
                                            • [코드스니펫] MainPage.js(숙제1)
                                            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton04} onPress={()=>{category('꿀팁 찜')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity> </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20, }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
                                          • 숙제 2: 꿀팁 찜 페이지가 될 아래의 화면을 만들어보세요!
                                            • [코드스니펫] StackNavigator.js(숙제2)
                                            • import React from 'react'; //설치한 스택 네비게이션 라이브러리를 가져옵니다 import { createStackNavigator } from '@react-navigation/stack'; //페이지로 만든 컴포넌트들을 불러옵니다 import DetailPage from '../pages/DetailPage'; import MainPage from '../pages/MainPage'; import AboutPage from '../pages/AboutPage'; import LikePage from '../pages/LikePage'; //스택 네비게이션 라이브러리가 제공해주는 여러 기능이 담겨있는 객체를 사용합니다 //그래서 이렇게 항상 상단에 선언하고 시작하는게 규칙입니다! const Stack = createStackNavigator(); const StackNavigator = () =>{ return ( //컴포넌트들을 페이지처럼 여기게끔 해주는 기능을 하는 네비게이터 태그를 선언합니다. //위에서 선언한 const Stack = createStackNavigator(); Stack 변수에 들어있는 태그를 꺼내 사용합니다. //Stack.Navigator 태그 내부엔 페이지(화면)를 스타일링 할 수 있는 다양한 옵션들이 담겨 있습니다. <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: "white", borderBottomColor: "white", shadowColor: "white", height:100 }, //헤더의 텍스트를 왼쪾에 둘지 가운데에 둘지를 결정 headerTitleAlign:'left', headerTintColor: "#000", headerBackTitleVisible: false }} > {/* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣습니다. 이 자체로 이제 페이지 기능을 합니다*/} <Stack.Screen name="MainPage" component={MainPage}/> <Stack.Screen name="DetailPage" component={DetailPage}/> <Stack.Screen name="AboutPage" component={AboutPage}/> <Stack.Screen name="LikePage" component={LikePage}/> </Stack.Navigator> ) } export default StackNavigator;
                                            • [코드스니펫] LikeCard.js(숙제2)
                                            • import React from 'react'; import {View, Image, Text, StyleSheet,TouchableOpacity} from 'react-native' //MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용 export default function LikeCard({content,navigation}){ return( //카드 자체가 버튼역할로써 누르게되면 상세페이지로 넘어가게끔 TouchableOpacity를 사용 <View style={styles.card}> <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> ) } const styles = StyleSheet.create({ card:{ flex:1, 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", } });
                                            • [코드스니펫] MainPage.js(숙제2)
                                            • import React,{useState,useEffect} from 'react'; import main from '../assets/main.png'; import { StyleSheet, Text, View, Image, TouchableOpacity, ScrollView} from 'react-native'; import data from '../data.json'; import Card from '../components/Card'; import Loading from '../components/Loading'; import { StatusBar } from 'expo-status-bar'; export default function MainPage({navigation,route}) { console.disableYellowBox = true; //return 구문 밖에서는 슬래시 두개 방식으로 주석 //기존 꿀팁을 저장하고 있을 상태 const [state,setState] = useState([]) //카테고리에 따라 다른 꿀팁을 그때그때 저장관리할 상태 const [cateState,setCateState] = useState([]) //컴포넌트에 상태를 여러개 만들어도 됨 //관리할 상태이름과 함수는 자유자재로 정의할 수 있음 //초기 상태값으로 리스트, 참거짓형, 딕셔너리, 숫자, 문자 등등 다양하게 들어갈 수 있음. const [ready,setReady] = useState(true) useEffect(()=>{ //뒤의 1000 숫자는 1초를 뜻함 //1초 뒤에 실행되는 코드들이 담겨 있는 함수 setTimeout(()=>{ //헤더의 타이틀 변경 navigation.setOptions({ title:'나만의 꿀팁' }) //꿀팁 데이터로 모두 초기화 준비 let tip = data.tip; setState(tip) setCateState(tip) setReady(false) },1000) },[]) const category = (cate) => { if(cate == "전체보기"){ //전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화 setCateState(state) }else{ setCateState(state.filter((d)=>{ return d.category == cate })) } } let todayWeather = 10 + 17; let todayCondition = "흐림" //처음 ready 상태값은 true 이므로 ? 물음표 바로 뒤에 값이 반환(그려짐)됨 //useEffect로 인해 데이터가 준비되고, ready 값이 변경되면 : 콜론 뒤의 값이 반환(그려짐) return ready ? <Loading/> : ( /* return 구문 안에서는 {슬래시 + * 방식으로 주석 */ <ScrollView style={styles.container}> <StatusBar style="black" /> {/* <Text style={styles.title}>나만의 꿀팁</Text> */} <Text style={styles.weather}>오늘의 날씨: {todayWeather + '°C ' + todayCondition} </Text> <TouchableOpacity style={styles.aboutButton} onPress={()=>{navigation.navigate('AboutPage')}}> <Text style={styles.aboutButtonText}>소개 페이지</Text> </TouchableOpacity> <Image style={styles.mainImage} source={main}/> <ScrollView style={styles.middleContainer} horizontal indicatorStyle={"white"}> <TouchableOpacity style={styles.middleButtonAll} onPress={()=>{category('전체보기')}}><Text style={styles.middleButtonTextAll}>전체보기</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton01} onPress={()=>{category('생활')}}><Text style={styles.middleButtonText}>생활</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton02} onPress={()=>{category('재테크')}}><Text style={styles.middleButtonText}>재테크</Text></TouchableOpacity> <TouchableOpacity style={styles.middleButton03} onPress={()=>{category('반려견')}}><Text style={styles.middleButtonText}>반려견</Text></TouchableOpacity> **<TouchableOpacity style={styles.middleButton04} onPress={()=>{navigation.navigate('LikePage')}}><Text style={styles.middleButtonText}>꿀팁 찜</Text></TouchableOpacity>** </ScrollView> <View style={styles.cardContainer}> {/* 하나의 카드 영역을 나타내는 View */} { cateState.map((content,i)=>{ return (<Card content={content} key={i} navigation={navigation}/>) }) } </View> </ScrollView> ); } const styles = StyleSheet.create({ container: { //앱의 배경 색 backgroundColor: '#fff', }, title: { //폰트 사이즈 fontSize: 20, //폰트 두께 fontWeight: '700', //위 공간으로 부터 이격 marginTop:50, //왼쪽 공간으로 부터 이격 marginLeft:20 }, weather:{ alignSelf:"flex-end", paddingRight:20 }, mainImage: { //컨텐츠의 넓이 값 width:'90%', //컨텐츠의 높이 값 height:200, //컨텐츠의 모서리 구부리기 borderRadius:10, marginTop:20, //컨텐츠 자체가 앱에서 어떤 곳에 위치시킬지 결정(정렬기능) //각 속성의 값들은 공식문서에 고대로~ 나와 있음 alignSelf:"center" }, middleContainer:{ marginTop:20, marginLeft:10, height:60 }, middleButtonAll: { width:100, height:50, padding:15, backgroundColor:"#20b2aa", borderColor:"deeppink", borderRadius:15, margin:7 }, 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" }, middleButtonTextAll: { color:"#fff", fontWeight:"700", //텍스트의 현재 위치에서의 정렬 textAlign:"center" }, cardContainer: { marginTop:10, marginLeft:10 }, aboutButton: { backgroundColor:"pink", width:100, height:40, borderRadius:10, alignSelf:"flex-end", marginRight:20, marginTop:10 }, aboutButtonText: { color:"#fff", textAlign:"center", marginTop:10 } });
                                            • [코드스니펫] LikePage.js(숙제2)
                                            • import React,{useState, useEffect} from 'react'; import {ScrollView, Text, StyleSheet} from 'react-native'; import LikeCard from '../components/LikeCard'; import Card from '../components/Card'; export default function LikePage({navigation,route}){ const [tip, setTip] = useState([{ "idx":3, "category":"재테크", "title":"잠자는 내 돈을 찾아라", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney1.png?alt=media&token=491096e7-0b57-40a3-991b-b984193f8018>", "desc":"‘새는 돈’에는 미처 몰랐던 카드 포인트, 휴면예금이나 환급금도 포함됩니다. 확실히 파악하지 못한 잠자는 돈을 찾아보고 자투리 돈들을 모으는 것도 중요합니다. 케이블방송, 위성방송 서비스를 이용하면서 중복 납부한 요금, 셋톱박스 보증금 등 돌려받지 않은 돈이 있는지 확인 해보세요. 또, 카드 포인트 통합 조회 서비스를 이용해 여러 개의 카드 포인트가 모두 얼마인지 체크해두는 것이 좋습니다. 보험해약 환급금, 휴면 보험금이나 휴면 예금을 찾아보고 돌려받는 일도 요즘에는 어렵지 않습니다.", "date":"2020.09.09" }, { "idx":4, "category":"재테크", "title":"할인행사, 한정할인판매 문구의 함정 탈출!", "image": "<https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fmoney2.png?alt=media&token=9c9df304-16e8-4a6f-8ae4-1d3f9ad58134>", "desc":"‘안 사면 100% 할인’이라는 말 들어보셨나요? 견물생심, 좋은 물건을 보면 사고 싶기 마련입니다. 특히 대대적인 ‘할인 행사’ 중인 대형 마트에 갔을 때는 말할 것도 없겠죠. 따라서 생필품을 살 때, 한꺼번에 사서 사용하는 것보다 필요할 때 조금씩 구매하는 편이 좋습니다. 장을 보면서 대형마트에 자주 가다 보면 지금 필요한 것뿐 아니라 앞으로 필요할 것까지 사게 되어 지출이 커지기 때문입니다. 특히 할인 품목을 보면 뜻하지 않은 소비를 하는 경우도 많아진다. 홈쇼핑, 대형마트 등의 ‘할인행사’, ‘한정할인판매’ 등의 문구를 조심하세요. ", "date":"2020.09.09" }]) useEffect(()=>{ navigation.setOptions({ title:'꿀팁 찜' }) }) return ( { tip.map((content,i)=>{ return() }) } ) } const styles = StyleSheet.create({ container:{ backgroundColor:"#fff" } })

'취미 > ' 카테고리의 다른 글

앱 코딩 4주차(3)  (0) 2021.12.24
앱 코딩 4주차(2)  (0) 2021.12.24
앱 코딩 4주차(1)  (0) 2021.12.24
앱 2주차  (0) 2021.12.22
앱1~2일차  (0) 2021.12.21

관련글 더보기