내비게이션이란?
앱에 페이지 개념을 입혀주고, 웹 사이트를 이용하듯, 앱에서 만든 컴포넌트들을 페이 지화 시켜주고, 해당 페이지끼리 이동을 가능하게 해주는 라이브러리이다.
컴포넌트들을 페이지화
물론 이것 또한 쉽게 페이징을 도와주는 외부 라이브러리를 가져다가 사용한다.
react-navigation 공식문서: https://reactnavigation.org/
React Navigation | React Navigation
Routing and navigation for your React Native apps
reactnavigation.org
Expo에서 지원하고 있는 도구
결국 이 라이브러리도 Expo에서 지원하고 있는 도구로써, 앱을 만들기 위해 사용 중인 Expo와 궁합이 아주 잘 맞는다. 앱 내의 페이지 구성 및 이동까지 직접 적용해볼 텐데 이 라이브러리는 다양한 기능들을 가지고 있다. 가령 다음 화면과 같이, 앱 하단에 탭 버튼을 두고 해당 버튼을 누르면 연결되어 있는 페이지로 바로바로 이동할 수 있게 해주는 기능(열 앱에서 봤었던)도 가지고 있다. 따라서 이번 시간에 공부한 다음 틈틈이 다른 기능들도 적용해보자!
앱 하단에 탭 버튼
현재 갖추고 있는 페이지 구성 확인!
페이지 구성은 보통 pages 폴더 안에 생성해둔 컴포넌트로 구성한다. 나만의 꿀팁 앱에 MainPage.js 메인 페이지, DetailPage.js 상세 화면 페이지, 그리고 숙제로 만든 소개 화면인 AboutPage.js 까지 총 3 페이지를 가지고 있다.
세 개의 페이지
기본 설치 코드
리액트 네이티브에서 페이지 내비게이션을 구현하기란 조금 까다롭다. 그래서 딱 필요한 것만 가져와 설치하고, 적용해보자! 필요한 것이라고 했지만, 거의 대부분의 앱에 적용이 되어 있는 것들만 가져왔다. 아래 명령어들은 내비게이션을 사용하기 위해 필요한 기본 라이브러리들! 곧 배울 스택 내비게이션과 탭 내비게이션 기능은 추가적으로 라이브러리를 또 설치해줘야 한다. 아래 명령어를 차례차례 터미널에 넣어 실행해주자! 가장 마지막 줄은 꽤 길고, 이렇게 여러 라이브러리들을 띄어쓰기로 연결해서 한 번에 설치할 수도 있다.
npm
내비게이션 설치 코드
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
내비게이션 추가 설치
스택 내비게이션이란?
스택 내비게이션은 컴포넌트에 페이지 기능을 부여해주고 컴포넌트에서 컴포넌트로 이동, 즉 페이지 이동을 가능하게 해 준다.
스택 내비게이션
컴포넌트를 페이지화 시키는 스택 내비게이션은 다음과 같다. 페이지처럼 만든 컴포넌트를, 정말 페이지처럼 사용할 수 있게끔 페이지로 컴포넌트를 감싸 페이지로 만들어준다. 이렇게 만든 여러 페이지들을 책갈피 기능을 하는 스택 내비게이터에 모조리 등록시켜서, 언제든지 이 페이지 이동이 가능하게끔 해준다. 페이지는 Stack.Screen이라 부르며 책갈피는 Stack.Navigator 라 부른다.
책갈피 Stack.Navigator
createStackNavigator 사용해보기
설치 코드
yarn add @ react - navigation / stack
createStackNavigator 설치된 모습
적용하기
navigation 폴더 하나를 만들고 StackNavigator.js 파일을 만들어주고 StackNavigator 안에 다음 코드를 넣어주자!
더보기
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 ;
폴더와 파일 코드를 넣은 모습
스택 내비게이터 코드 분석
컴포넌트를 페이지화 했고, 페이지를 이동할 수 있는 내비게이션도 준비가 됐다면, 최상단 컴포넌트 즉 App.js 에 내비게이션 기능을 달아야 한다.
공식 문서 링크: https://reactnavigation.org/docs/stack-navigator/ 즉, 앱 가장 최상위 코드에 내비게이션을 다는 것이다. 그래야 앱 어디서든 원하는 페이지 이동이 가능할 테니까!
https://reactnavigation.org/docs/stack-navigator/
reactnavigation.org
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 >
main
4) 페이지 연결
{ /* 컴포넌트를 페이지로 만들어주는 엘리먼트에 끼워 넣는다. 이 자체로 이제 페이지 기능을 한다*/ }
< Stack.Screen name = "MainPage" component = { MainPage } />
< Stack.Screen name = "DetailPage" component = { DetailPage } />
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 모습!
MainPage
앱 화면
MainPage 화면은 다음과 같이 상단의 모습이 약간 이상해 보인다.
앱 상단
그 이유는 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 >
따라서 헤더 스타일을 적절히 바꾸기 위해 다음과 같이 옵션 부분의 코드를 수정해보도록 하자.
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 { 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 () {
//useState 사용법
//[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수
//setState는 state를 변경시킬때 사용해야하는 함수
//모두 다 useState가 선물해줌
//useState()안에 전달되는 값은 state 초기값
const [ state , setState ] = useState ([])
const [ cateState , setCateState ] = useState ([])
//하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수
//내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음
const [ ready , setReady ] = useState ( true )
useEffect (() => {
//뒤의 1000 숫자는 1초를 뜻함
//1초 뒤에 실행되는 코드들이 담겨 있는 함수
setTimeout (() => {
setState ( data . tip )
setCateState ( data . tip )
setReady ( false )
}, 1000 )
},[])
const category = ( cate ) => {
if ( cate == "전체보기" ){
//전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화
setCateState ( state )
} else {
setCateState ( state . filter (( d ) => {
return d . category == cate
}))
}
}
//data.json 데이터는 state에 담기므로 상태에서 꺼내옴
// let tip = state.tip;
let todayWeather = 10 + 17 ;
let todayCondition = "흐림"
//return 구문 밖에서는 슬래시 두개 방식으로 주석
return ready ? < Loading /> : (
/*
return 구문 안에서는 {슬래시 + * 방식으로 주석
*/
< ScrollView style = { styles . container } >
< StatusBar style = "light" />
{ /* <Text style={styles.title}>나만의 꿀팁</Text> */ }
< Text style = { styles . weather } > 오늘의 날씨: { todayWeather + '°C ' + todayCondition } </ Text >
< Image style = { styles . mainImage } source = { { uri : 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 옵션 코드 수정 후 모습
그래도 위에 페이지 제목으로 MainPage가 되는 모습은 뭔가 또 수정이 필요해 보인다. 이를 위해선 다음 스택 내비게이션 페이지 이동 강의에서 살펴보도록 하자!
페이지 이동하기
Main 페이지(MainPage.js)에서 카드 버튼을 누르면(Card.js) 꿀팁 상세 페이지(DetailPage.js)로 이동한다. 일단 페이지를 이동시키려면, 책갈피가 페이지들에게 부여해준 페이지 이동 기능을 사용해야 한다. Stack.screen에 등록된 모든 페이지 컴포넌트들은 navigation와 route라는 딕셔너리(객체)를 속성으로 넘겨받아 사용할 수 있고, 이 두 딕셔너리는 다음과 같은 기능을 갖는다.
//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 ;
먼저 구조를 확인해봤다. 이제 사용을 해봐야 감이 오겠지? 그럼 이제 Card.js에 페이지 이동 기능을 달아보자. 코드를 실행 한 다음 페이지를 이동해 보면 익숙한 뒤로 가기 버튼도 보일 것이다!
데이터 없이 페이지 이동하기
navigation . navigate ( "DetailPage" )
MainPage.js
더보기
import React ,{ useState , useEffect } from 'react' ;
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 }) {
//useState 사용법
//[state,setState] 에서 state는 이 컴포넌트에서 관리될 상태 데이터를 담고 있는 변수
//setState는 state를 변경시킬때 사용해야하는 함수
//모두 다 useState가 선물해줌
//useState()안에 전달되는 값은 state 초기값
const [ state , setState ] = useState ([])
const [ cateState , setCateState ] = useState ([])
//하단의 return 문이 실행되어 화면이 그려진다음 실행되는 useEffect 함수
//내부에서 data.json으로 부터 가져온 데이터를 state 상태에 담고 있음
const [ ready , setReady ] = useState ( true )
useEffect (() => {
//뒤의 1000 숫자는 1초를 뜻함
//1초 뒤에 실행되는 코드들이 담겨 있는 함수
setTimeout (() => {
//헤더의 타이틀 변경
navigation . setOptions ({
title : '나만의 꿀팁'
})
setState ( data . tip )
setCateState ( data . tip )
setReady ( false )
}, 1000 )
},[])
const category = ( cate ) => {
if ( cate == "전체보기" ){
//전체보기면 원래 꿀팁 데이터를 담고 있는 상태값으로 다시 초기화
setCateState ( state )
} else {
setCateState ( state . filter (( d ) => {
return d . category == cate
}))
}
}
//data.json 데이터는 state에 담기므로 상태에서 꺼내옴
// let tip = state.tip;
let todayWeather = 10 + 17 ;
let todayCondition = "흐림"
//return 구문 밖에서는 슬래시 두개 방식으로 주석
return ready ? < Loading /> : (
/*
return 구문 안에서는 {슬래시 + * 방식으로 주석
*/
< ScrollView style = { styles . container } >
< StatusBar style = "light" />
{ /* <Text style={styles.title}>나만의 꿀팁</Text> */ }
< Text style = { styles . weather } > 오늘의 날씨: { todayWeather + '°C ' + todayCondition } </ Text >
< Image style = { styles . mainImage } source = { { uri : 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 ( "Detail" ,{
title : title
})
그럼 Card에서 DetailPage로 이동할 때, MainPage로 부터 넘겨받은 content도 넘겨보자! 왜냐하면 지금은 어떠한 카드를 눌러도 상세 페이지에서 동일한 데이터를 보게 되니까! tip 데이터를 고정해놨으니까!
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" : "렌탈 서비스 금액 비교해보기" ,
"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'
}
})
이동한 디테일 페이지 모습
건네받은 값을 꺼낼 땐, 다음과 같이 책갈피가 navigation과 추가적으로 건네준 route에서 꺼내 확인할 수 있다. route.params 객체에 건네준 딕셔너리가 넘겨 있다!
route에 담겨져 오는 데이터 콘솔에서 직접 확인
DetailPage 에서 상태 값을 초기에 설정한 이유
DetailPage 초반에 우린 이렇게 상태 값을 설정해놨었다. 그 이유가 뭘까? 심지어 이 상태 값을 초기에 설정 안 하면 오류가 발생한다. tip엔 아무것도 없다며...
//초기 컴포넌트의 상태값을 설정
const [ tip , setTip ] = useState ({
"idx" : 9 ,
"category" : "재테크" ,
"title" : "렌탈 서비스 금액 비교해보기" ,
"desc" : "요즘은 정수기, 공기 청정기, 자동차나 장난감 등 다양한 대여서비스가 활발합니다. 사는 것보다 경제적이라고 생각해 렌탈 서비스를 이용하는 분들이 늘어나고 있는데요. 다만, 이런 렌탈 서비스 이용이 하나둘 늘어나다 보면 그 금액은 겉잡을 수 없이 불어나게 됩니다. 특히, 렌탈 서비스는 빌려주는 물건의 관리비용까지 포함된 것이기에 생각만큼 저렴하지 않습니다. 직접 관리하며 사용할 수 있는 물건이 있는지 살펴보고, 렌탈 서비스 항목에서 제외해보세요. 렌탈 비용과 구매 비용, 관리 비용을 여러모로 비교해보고 고민해보는 것이 좋습니다. " ,
"date" : "2020.09.09"
})
그 이유는 컴포넌트가 화면에 그려지는 순서에 있다. 이건 지금 단계에선 조금 과한감이 있어서 간략히 만 설명해보면 다음과 같다.
DetailPage 컴포넌트가 useState에 들어 있는 데이터 가지고 화면에 그려짐(return 함수 실행).
화면에 다 그려진 후, useEffect 함수 실행.
useEffect에서 상태 값 변경 이벤트가 실행되면 변경된 데이터 가지고 다시 return 실행.
변경된 데이터를 가지고 화면에 DetailPage가 다시 그려짐.
곰곰이 생각해보면 결국, 리액트 네이티브에서 화면이 변경되는 시점은 컴포넌트의 상태 값이 변경될 때이니까! 처음에는, 어떠한 데이터를 보여주는 컴포넌트라면, 무조건 초기값을 의미 없는 값이더라도 넣고 시작한다!라고 생각하자! 또는 한번 배웠던 Loading.js를 이용하여 데이터가 준비가 되면 로딩 화면을 치우고, 본 화면을 보여주는 처리를 해보면 된다.