안녕하세요.
쌩광부입니다.
아래 도표는 우리가 만들어야될 각각의 스크린을 표시했는데요.
Welcome - 시작화면
MyWallet - 내 지갑
AddWallet - 지갑 생성하기
ImportWallet - 지갑 불러오기
WalletDetail - 지갑 상세보기
Transactions - 거래내역 보기
SendCoin/Token - 코인/토큰 보내기
그냥 제목만 봐도 각각의 화면이 어떤 역할을 하는지 아시겠죠?
#3. Navigation 완성하기
그럼 이번 강좌에서는 각각의 화면을 만들고 네비게이션으로 연결하는 방법을 알려드립니다.
먼저 추가로 필요한 패키지를 설치합니다.
C:\Users\YOUR_USERNAME\topwallet> npm install react-native-actionsheet --save
ActionSheet는 iOS에서 자주볼 수 있는 팝업 메뉴인데요.
원래 RN에서는 ActionSheetIOS를 기본적으로 지원하지만 문제는 Android에서는 사용할 수가 없어요.
그렇기 때문에 별도의 Android, iOS 공용 패키지인 react-native-actionsheet를 이용하지요.
react-native-actionsheet 패키지에 대한 자세한 설명은 아래 링크를 참고하세요.
https://github.com/beefe/react-native-actionsheet
자 이제 본격적으로 코딩을 해볼까요?
우선 아래와 같은 소스 파일들을 ./src/screen 폴더에 생성하세요.
mywallet.js, addwallet.js, importwallet.js, welletdetail.js, transactions.js, sendcoin.js, icon-button.js
맨 마지막에 icon-button.js 파일은 Icon 형태의 버튼을 표시하기 위해 필요합니다.
mywallet.js 파일을 아래와 같이 코딩합니다.
import React, { Component } from 'react';
import { View, Text, Button } from 'react-native';
import ActionSheet from 'react-native-actionsheet'
import { IconButton } from './icon-button';
class MyWalletScreen extends Component {
static navigationOptions =({navigation}) => ({
title: '내 지갑',
headerRight: (
<View style={{marginRight: 5}}>
<IconButton onPress={() => navigation.getParam('showMenu')() }
source={require('../img/ico-menu.png')} />
</View>
)
});
componentWillMount() {
this.props.navigation.setParams({showMenu: () => this.ActionSheet.show()});
}
render() {
return (
<View style={{padding: 5}}>
<Text style={{marginBottom: 10}}>내 지갑 화면</Text>
<Button onPress={() => this.props.navigation.navigate('WalletDetail')}
title="지갑 상세보기"/>
<View style={{marginBottom: 10}}/>
<Button onPress={() => this.props.navigation.navigate('Transactions')}
title="거래내역 보기"/>
<ActionSheet
ref={o => this.ActionSheet = o}
title={'메뉴를 선택하세요.'}
options={['지갑 생성하기', '지갑 불러오기', '취소']}
cancelButtonIndex={2}
destructiveButtonIndex={99}
onPress={(index) => {
if(index == 0) this.props.navigation.navigate('AddWallet');
else if(index == 1) this.props.navigation.navigate('ImportWallet');
}}
/>
</View>
);
}
}
export default MyWalletScreen;
한줄 한줄 설명하겠습니다.
import { View, Text, Button } from 'react-native';
필요한 UI Component 패키지들을 불러옵니다.
import ActionSheet from 'react-native-actionsheet'
ActionSheet를 사용하기 위해 불러오기를 했습니다.
import { IconButton } from './icon-button';
아이콘 버튼을 표시하기 위해 불러오기를 했는데요. 좀 뒤에 코딩합니다.
여기서 잠깐!
위에 2개의 import를 보시면 from 뒤에 ./가 있는게 있고 없는게 있고 그렇죠?
./가 있다는 것은 현재 내 소스 파일이 있는 폴더에서 지정된 파일을 찾는다는 얘기고요.
./가 없다면 미리 등록된 패키지들 중에서 찾아옵니다.
물론 같은 폴더의 있는 파일들을 불러올때는 ./를 사용하지 않아도 됩니다.
그러나 ./를 지정함으로써 명확하게 이 파일은 내 소스에서 있다고 명시해줘야 차후 설치된 패키지들과의 충돌을 막을 수 있습니다.
title: '내 지갑'
이 화면의 이름을 '내 지갑'이라고 출력합니다.
headerRight: (
<View style={{marginRight: 5}}>
<IconButton onPress={() => navigation.getParam('showMenu')() } source={require('../img/ico-menu.png')} />
</View>
)
화면 네비게이션바에 우측(headerRight)에 메뉴 버튼을 만듭니다.
그리고 onPress 프로퍼티에 버튼이 눌렸을때 호출할 명령을 작성합니다.
source 프로퍼티에는 메뉴 아이콘을 넣습니다.
아이콘은 아래에서 불러와서 ./src/img 폴더에 넣습니다.
https://github.com/topmining/topwallet/blob/master/src/img/ico-menu.png
여기서 잠깐!
지난 강좌에서 설명했듯이 < >안에 표시되는 것들은 Component를 라고 하고요.
그 다음 style, onPress, source 들을 우리는 프로퍼티(속성)라고 부릅니다.
그리고 이런 것들을 총칭해서 props라고 합니다.
앞으로의 소스에 보시면 props 라는 표현을 많이 사용하게 되는데요.
전부 프로퍼티(속성)를 의미합니다.
이런 props는 해당 Component의 고유 속성이라고 보시면 되겠습니다.
결론적으로 props를 어떻게 설정하고 어떻게 변경하느냐에 따라서 모양이 바뀌거나 프로그램의 흐름이 변경되거나 하겠죠. ^^
onPress={() => navigation.getParam('showMenu')() }
Component가 눌렸을때 navigation.getParam을 호출해서 showMenu를 불러와서 호출한다는 의미인데요. 어렵죠?
react-navigation을 사용할 경우 Navigation에서 우리가 만드는 Screen의 특정 함수를 직접 호출할 수 없기 때문에 우리가 만든 함수를 Navigation의 params로 연결하고 Navigation은 params를 다시 불러서 함수를 호출합니다. 복잡한 과정이지만 또 지금은 이해가 잘 안가시겠지만.
그냥 그렇구나 하고 그대로 코딩하시면 되겠습니다.
componentWillMount()
Component가 화면에 그려지기 직전에 호출되는 함수입니다.
this.props.navigation.setParams({showMenu: () => this.ActionSheet.show()});
이 Component의 props에서 navigation을 불러오고 거기에 showMenu 속성에 이 Component의 ActionSheet.show 함수를 지정합니다. 이건 또 뭔소리?
하나 하나 살펴보면
this 이건 지금 실행되고 있는 이 Component 자신을 의미합니다.
props 이건 위쪽에 설명했던 고유속성을 말하는 거고요.
navigation 이건 저희가 react-navigation을 사용하는데요. 그런 경우 각각의 Screen으로 만든 Component들은 props에 navigation 속성이 추가됩니다.
즉, navigation과 데이터를 주고 받기 위해서는 이 속성이 꼭 필요하겠죠.
더 궁금하셔도 그냥 코딩하세요. 위와 같은 표현은 지속적으로 나오기 때문에 그냥 그렇게 해야되는구나 생각하시면 됩니다.
물론 다른 방법도 있겠지만 저희는 초보니까요. ^^
render() 함수에서는 간략하게 이 화면이 '내 지갑 화면'이라는 것을 출력하고요.
<Button onPress={() => this.props.navigation.navigate('WalletDetail')} title="지갑 상세보기"/>
지갑 상세보기 화면으로 이동하기 위한 버튼입니다.
<Button onPress={() => this.props.navigation.navigate('Transactions')} title="거래내역 보기"/>
거래내역 보기 화면으로 이동하기 위한 버튼입니다.
<ActionSheet
ref={o => this.ActionSheet = o}
title={'메뉴를 선택하세요.'}
options={['지갑 생성하기', '지갑 불러오기', '취소']}
cancelButtonIndex={2}
destructiveButtonIndex={99}
onPress={(index) => {
if(index == 0) this.props.navigation.navigate('AddWallet');
else if(index == 1) this.props.navigation.navigate('ImportWallet');
}}
/>
ActionSheet를 화면에 표시하기위한 코딩입니다.
지갑 생성하기, 지갑 불러오기 메뉴를 표시하고 각각을 클릭했을때 각각의 화면으로 이동하는 코딩이 되어있습니다.
나중에 실행해 보시면 아! 이런거구나 알게됩니다.
onPress에는 버튼이 눌렸을때 각각 어느 화면으로 이동하는지를 설정합니다.
this.props.navigation.navigate('AddWallet')
AddWallet 화면으로 이동하라는 명령입니다.
위와 같이 this.props.navigation.navigate 함수를 호출하면 간단하게 다른 화면으로 이동할 수 있습니다.
icon-button.js 파일은 아래와 같이 코딩합니다.
import React from 'react';
import { StyleSheet, TouchableHighlight, Image } from 'react-native';
export const IconButton = ({ source, onPress, style }) => (
<TouchableHighlight style={[styles.container, style]} onPress={onPress}
underlayColor="#d0d0d0">
<Image style={styles.image} source={source} />
</TouchableHighlight>
);
const styles = StyleSheet.create({
container: {
width: 42,
height: 42,
alignItems: 'center',
justifyContent: 'center',
borderRadius: 21
},
image: {
width: 32,
height: 32
}
});
react-native에 기본적인 아이콘 버튼이 없기 때문에 별도로 Component를 제작했습니다.
const styles = StyleSheet.create
이곳을 보시면 버튼의 모양을 정의하는 내용이 포함됩니다.
style에 대한 내용은 차후에 다시 설명하도록 하겠습니다.
addwallet.js, importwallet.js, welletdetail.js, transactions.js, sendcoin.js 파일의 소스는 깃허브에서 확인하시고 그대로 코딩하시면 됩니다.
https://github.com/topmining/topwallet/tree/master/src/screen
여기까지해서 각각의 페이지들을 만들어봤는데요.
이 상태로 실행해봤자 맨 첫 'MyWallet(내 지갑)' 페이지를 연결해주지 않았기 때문에 재대로 실행되지 않겠죠.
기존에 만들었던 App.js 파일을 열어 아래와 같이 수정합니다.
import React from 'react';
import { createStackNavigator } from 'react-navigation';
import WelcomeScreen from './src/screen/welcome';
import MyWalletScreen from './src/screen/mywallet';
import AddWalletScreen from './src/screen/addwallet';
import ImportWalletScreen from './src/screen/importwallet';
import WalletDetailScreen from './src/screen/welletdetail';
import TransactionsScreen from './src/screen/transactions';
import SendCoinScreen from './src/screen/sendcoin';
const App = createStackNavigator({
Welcome: { screen: WelcomeScreen },
MyWallet: { screen: MyWalletScreen },
AddWallet: { screen: AddWalletScreen },
ImportWallet: { screen: ImportWalletScreen },
WalletDetail: { screen: WalletDetailScreen },
Transactions: { screen: TransactionsScreen },
SendCoin: { screen: SendCoinScreen },
},
{
initialRouteName: "Welcome",
cardStyle: { backgroundColor: '#FFFFFF' },
});
export default App;
빨강색으로 표시된 부분이 추가된 부분인데요.
Navigation에 각각의 페이지를 등록해줘야 합니다.
이제 ./src/screen 폴더에 있는 welcome.js 파일을 열어 아래와 같이 수정합니다.
import React, { Component } from 'react';
import { View, Image } from 'react-native';
import { StackActions, NavigationActions } from 'react-navigation';
class WelcomeScreen extends Component {
static navigationOptions =({navigation}) => ({
title: "",
headerTransparent: true
});
componentDidMount() {
setTimeout(() => this.gotoMyWallet(), 1000);
}
gotoMyWallet() {
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'MyWallet' })],
});
this.props.navigation.dispatch(resetAction);
}
render() {
return (
<View style={{alignItems: 'center', justifyContent: 'center', flex: 1}}>
<Image source={require('../img/ico-esn.png')} />
</View>
);
}
}
export default WelcomeScreen;
마찬 가지로 빨강색으로 표시된 부분이 추가된 부분인데요.
componentDidMount()
Component가 화면에 그려진 이후 호출되는 함수인데요.
여기서 잠깐!
위에 설명했던 componentWillMount와 componentDidMount는 어떤 차이가 있을까요.
둘다 화면이 그려질때 호출되는 함수인데요.
componentWillMount은 화면이 그려지기 이전에 호출됩니다.
componentDidMount은 화면이 그려진 이후에 호출됩니다.
setTimeout(() => this.gotoMyWallet(), 1000);
1000ms(1초) 뒤에 gotoMyWallet 함수를 호출합니다.
gotoMyWallet() {
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'MyWallet' })],
});
this.props.navigation.dispatch(resetAction);
}
원래 this.props.navigation.navigate 함수를 호출해 다른 화면으로 이동한다고 설명했는데요.
여기서는 this.props.navigation.dispatch 함수를 이용해 이제 다시 뒤로 돌아올 필요가 없다고 지정하는 것입니다.
StackActions.reset을 이용해 Navigation 흐름을 삭제하고 MyWallet으로 이동합니다.
즉, WelcomeScreen은 다시 되돌아갈 이유가 없기 때문이죠.
만약
gotoMyWallet() {
this.props.navigation.navigate('MyWallet');
}
으로 코딩하고 실행하면 어떤 상황이 발생되는지는 직접 확인해보시면 좋겠네요.
이제 가상 머신을 뛰우고 실행을 해보겠습니다.
C:\Users\YOUR_USERNAME\topwallet> react-native run-android
버튼들을 하나씩 클릭해 보시면 아래와 같은 화면들을 볼 수 있습니다.
각 화면들에는 뒤로가기 버튼에 대한 어떠한 코딩도 없는데 희한하게 잘 작동합니다.
바로 react-navigation을 이용했기 때문에 기본적으로 뒤로가기 기능이 작동되는 것이지요.
언밀하게 저희는 StackNavigation기능을 이용한 것인데요.
그외에도 몇 가지 Navigation기능이 더 있습니다. 물론 저희는 이것으로 충분하지요. ^^
Tip. 가상 머신에서 RN 디버깅 단축키
가상 머신 실행창에서 Ctrl+M 버튼을 누르면 다양한 옵션을 사용 할 수 있는데요.
그중에 Enable Live Reload, Enable Hot Reloading 기능을 활성화하면 소스 파일이 저장 될때마다 자동으로 빌드되어 실행됩니다.
실제 개발시에는 이것을 활성화하고 코딩을 하면 매우 편리하겠죠.
만약 소스 코드에 오류가 있으면 빨간 배경의 오류창을 볼 수 있기 때문에 바로 바로 소스를 수정 할 수 있겠죠. ^^
오늘 강좌는 여기까지입니다.
이전 강좌를 보고 싶으시면 제 블로그를 방문해주세요.
https://www.ddengle.com/@TopMining
좋아요 많이 부탁합니다.
이해가 안돼서 무지 답답합니다.ㅠ