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>);
}
--> 처음에 시작되는 js 파일
--> 시작하면 스택네비게이터를 관리하기 위해 만든 StackNavigator.js 파일을 불러온다.
--> 위의 태그는 NavigationContainer로 감싸여있다.(Navigator를 감싸는 역할을 하는 태그)
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;
--> 스택 네비게이터를 관리하기 위한 js 파일이다.
--> 네비게이터를 통해 이동하기 위해서 앱에서 사용할 컴포넌트들을 페이지로서 등록해줘야한다.
<Stack.Screen name="MainPage" component={MainPage}/>
<Stack.Screen name="DetailPage" component={DetailPage}/>
--> 이 때 MainPage쪽이 제일 위쪽에서 등록되었기 떄문에 앱에는 MainPage가 화면에 등장한다.(호출된다.)
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
},
});
......
import Loading from '../components/Loading';
import { StatusBar } from 'expo-status-bar';
export default function MainPage({navigation,route}) {
console.disableYellowBox = true;
//return 구문 밖에서는 슬래시 두개 방식으로 주석
.....
---> export default function MainPage({navigation,route}) { 부분에서 속성으로 navigation과 route를 받음
--> MainPage.js는 이 앱의 논리상 바로 위의 StackNavigator.js 파일에서 태그 형식으로 호출된다.
따라서 navigation과 route를 속성(props)으로서 받아 MainPage가 사용할 수 있게 된 것이다.
.....
useEffect(()=>{
//뒤의 1000 숫자는 1초를 뜻함
//1초 뒤에 실행되는 코드들이 담겨 있는 함수
setTimeout(()=>{
//헤더의 타이틀 변경
navigation.setOptions({
title:'나만의 꿀팁'
})
......
--> 예) 아래의 card 태그에 navigator속성으로 navigator를 전달하여, card.js 파일에서 해당 컴포넌트를 클릭하면 다른 컴포넌트로 이동하는 설정할 할 수 있게 만듬
<View style={styles.cardContainer}>
{/* 하나의 카드 영역을 나타내는 View */}
{
cateState.map((content,i)=>{
return (<Card content={content} key={i} navigation={navigation}/>)
})
}
</View>
--> 즉, MainPage.js의 경우 StackNavigator의 Stack.Screen태그를 통해 직접 호출되었기 때문에 navigation을 속성으로 전달받아 사용할 수 있다.
--> 하지만 태그로 삽입된 js파일인 Card의 경우, navigation속성을 따로 받지 못했기 때문에 이대로는 내부적으로 스택 네비게이터를 통한 컴포넌트 이동설정을 해줄 수 없다.
--> 따라서 속성으로 navigation을 넘겨주어 스택네비게이션을 통한 컴포넌트 이동이 내부적으로 가능하도록 만든 것이다.
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",
}
});
//MainPage로 부터 navigation 속성을 전달받아 Card 컴포넌트 안에서 사용
export default function Card({content,navigation}){
return(
<TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage')}}>
--> 해당 컴포넌트 클릭 시 navigation.navigate('DetailPage')함수를 실행하여 DetailPage라는 name속성으로 Stack.Screen에 등록된 컴포넌트로 이동함
<TouchableOpacity style={styles.card} onPress={()=>{navigation.navigate('DetailPage',content)}}>
--> navigate함수에 전달할 데이터를 두번째 매개변수로 추가해주면 해당 매개변수를 통해 데이터가 전달된다.
--> 해당 content는 card의 내용(제목,카테고리,내용 등등)에 해당하는 딕셔너리 데이터
--> 이것을 DetailPage에 전달하여 상세페이지가 해당 내용을 설명할 수 있게 할 것임
[여기서 사용된 content 딕셔너리 중 하나 예시]
{
"idx":0,
"category":"생활",
"title":"먹다 남은 피자를 촉촉하게!",
"image":"https://firebasestorage.googleapis.com/v0/b/sparta-image.appspot.com/o/lecture%2Fpizza.png?alt=media&token=1a099927-d818-45d4-b48a-7906fd0d2ad3",
"desc":"먹다 남은 피자는 수분이 날라가기 때문에 처음처럼 맛있게 먹을 수 없는데요. 이럴 경우 그릇에 물을 받아 전자레인지 안에서 1분 30초에서 2분 정도 함께 돌려주면 촉촉하게 먹을 수 있습니다. 물이 전자레인지 안에서 수증기를 일으키고, 피자에 촉촉함을 더해줍니다.",
"date":"2020.09.09"
},
.......
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 값을 적절히 잘 이용해야 합니다.
<ScrollView style={styles.container}>
<Image style={styles.image} source={{uri:tip.image}}/>
<View style={styles.textContainer}>
<Text style={styles.title}>{tip.title}</Text>
<Text style={styles.desc}>{tip.desc}</Text>
<TouchableOpacity style={styles.button} onPress={()=>popup()}><Text style={styles.buttonText}>팁 찜하기</Text></TouchableOpacity>
</View>
</ScrollView>
)
}
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'
}
})
export default function DetailPage({navigation,route}) {
navigation --> 해당 임포넌트에서 다른 임포넌트로 이동할 때 사용
route --> 다른 임포넌트에서 해당 임포넌트로 이동할 때 보내온 데이터가 있으면 그 데이터를 받는데 사용
Card.js에서 navigation.navigate 함수를 쓸때 두번째 인자로 content를 넘겨줬죠?
content는 딕셔너리 그 자체였으므로 route.params에 고대~로 남겨옵니다.
즉, route.params 는 content죠!
//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"
})
--> useState의 데이터값이 비어있는 상태에서 화면을 그리라고 하면 오류 발생
방법 1) 로딩화면 구현하여, 이후 바로 useEffect 를 실행시켜 초기화
방법 2) 의미없는 데이터를 이용하여 useState의 초기값을 직접 설정
--> 위에서는 2번 방법 사용중임
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)
},[])
Card.js에서 navigation.navigate 함수를 쓸때 두번째 인자로 content를 넘겨줬죠?
content는 딕셔너리 그 자체였으므로 route.params에 고대~로 남겨옵니다.
즉, route.params 는 content죠!
--> 그 데이터를 useState의 데이터로 할당 --> 위 코드에는 tip이라는 이름으로 설정되어있음
setTip(route.params)
navigation.setOptions({
//setOptions로 페이지 타이틀도 지정 가능하고
title:route.params.title,
//StackNavigator에서 작성했던 옵션을 다시 수정할 수도 있습니다.
headerStyle: {
backgroundColor: '#000',
shadowColor: "#000",
},
headerTintColor: "#fff",
})
<ScrollView style={styles.container}>
<Image style={styles.image} source={{uri:tip.image}}/>
<View style={styles.textContainer}>
<Text style={styles.title}>{tip.title}</Text>
<Text style={styles.desc}>{tip.desc}</Text>
<TouchableOpacity style={styles.button} onPress={()=>popup()}><Text style={styles.buttonText}>팁 찜하기</Text></TouchableOpacity>
</View>
</ScrollView>
navigation --> 해당 임포넌트에서 다른 임포넌트로 이동할 때 사용
navigation.navigate(이동하고자 하는 컴포넌트가 Stack.Screen에 등록할 때의 name속성명)
route --> 다른 임포넌트에서 해당 임포넌트로 이동할 때 보내온 데이터가 있으면 그 데이터를 받는데 사용 route.params에 저장되어있음