2016年10月16日 星期日

Reactive Native 初體驗

話說IONIC2 Angular2 的組合仍然是當前火紅的HyBrid APP, 但開發行動裝置的語言工具又多了一款React Native 可直接呼叫原生的API,而且語法近似乎HTML5 CSS3,所以讓我嘗鮮看看,找到一款完美的跨平台語法是每個人的夢想。

一、建立react native 的專案


react-native init AwesomeProject
cd AwesomeProject
react-native run-android

二、TabNavigator 範例:

適用於IOSAndroid Navigator Tab (下方的工具列表上,可切換的頁籤),因為是第三方元件提供的,所以要先安裝

npm install react-native-tab-navigator --save

範例: index.android.js 建立有兩個頁籤的TAB,分別是頁面Featured Search

首先開始使用react 要先import 基本元件:

import React, { Component,PropTypes } from 'react';
import {
AppRegistry, //用來預設畫面
StyleSheet, //如同css一樣定義文字大小、排版
Text, //顯示文字
View //每個畫面==>screen
} from 'react-native';
import TabNavigator from 'react-native-tab-navigator'; //引用外部的元件,所以要import

TabNavigator 的第三方元件可以自訂新增tab 頁籤,每頁籤可引用自訂react component 控件,例如以下引用兩個【Featured】和【Search】在不同component控件

import React, { Component,PropTypes } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
import TabNavigator from 'react-native-tab-navigator';

var Featured = require('./Featured');
var ListViewBasics = require('./ListViewDemo');

class AwesomeProject extends Component {
constructor(props) {
super(props);
this.state = {
selectedTab: 'Featured' //預設為Featured
};
}

render() {
return (
<TabNavigator>
    <TabNavigator.Item selected={this.state.selectedTab === 'Featured'}
      title="Featured" onPress={() => this.setState({ selectedTab: 'Featured' })}>
         <Featured />//零組件
   </TabNavigator.Item>
   <TabNavigator.Item selected={this.state.selectedTab === 'ListViewBasics'}
        title="ListViewBasics" onPress={() => this.setState({ selectedTab: 'ListViewBasics' })}>
        <ListViewBasics />   //零組件
   </TabNavigator.Item>
</TabNavigator>
);
}
}

AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject); //用來當作預設頁的元件

設定 StyleSheet
const styles = StyleSheet.create({
container: {
    flex: 1, //用來在各種不同螢幕大小,元件layout與邊框距離
    justifyContent: 'center', //置中處理
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
},
welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
},
instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
},
});

三、Navigator (頁面的轉換路徑)
Navigator 元件允許頁面轉向到另一個新的頁面,接續上面的案例中,Featured是獨立的控件,要能導覽至下一頁MyScene,按下back就回上一頁,所以要先import Navigator元件和MyScene 控件,才能將畫面導覽至MyScene

Featured.js
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Navigator
} from 'react-native';
import MyScene from './MyScene'; //元件,要自已實作
var styles = StyleSheet.create({
    description: {
    fontSize: 20,
    backgroundColor: 'white'
},
container: {
   flex: 1,
   justifyContent: 'center',
   alignItems: 'center'
}
});
class Feature extends Component {
render() {
return (
<Navigator
initialRoute={{title:"my Initial Scene", index:0}} //傳給下一頁的參數值(Title,index)
renderScene={(route,navigator)=>{
return <MyScene title={route.title}
onForward={()=>{
const nextIndex=route.index+1;
    navigator.push({
    title:"Scene_"+nextIndex,
    index:nextIndex,
});
}}
onBack={() => {
if (route.index > 0) {
     navigator.pop();
}
}}
/>
}}
/>
);
}
}
module.exports = Feature;

名稱 MyScene的自訂屬性 onForward onBack,傳入函式用來控制導覽下一頁 navigator.push 的動作和上一頁 navigator.pop()的動作。

MyScene.js
import React, { Component,PropTypes } from 'react';
import { View, Text, Navigator,StyleSheet,TouchableHighlight } from 'react-native';

var styles = StyleSheet.create({
toolbar:{
backgroundColor:'#81c04d',
paddingTop:30,
paddingBottom:10,
flexDirection:'row' //用來控制以列(由左->)的排列
},
toolbarButton:{
width: 50,
color:'#fff',
textAlign:'center'
},
toolbarTitle:{
color:'#fff',
textAlign:'center',
fontWeight:'bold',
flex:1
}
});

class MyScene extends Component {

static PropTypes={
title: PropTypes.string.isRequired,
onForward:PropTypes.func.isRequired,
onBack:PropTypes.func.isRequired
}
render() {
return (
<View style={styles.toolbar}>
<TouchableHighlight onPress={this.props.onBack}>
<Text style={styles.toolbarButton}>back</Text>
</TouchableHighlight>
<Text style={styles.toolbarTitle}> {this.props.title}</Text>
<TouchableHighlight onPress={this.props.onForward}>
<Text style={styles.toolbarButton}>next</Text>
</TouchableHighlight>
</View>
)
}
}
module.exports=MyScene;

<TouchableHighlight> 控制允許點選按鈕範圍,所以如上所示 back next是按鈕,各分別按下後呼叫不同的事件產生, {this.props.onBack}{this.props.OnFoward} 調用MyScene傳入參數的OnFoward OnBack 的方法。



ListViewDemo.js

import React, { Component } from 'react';
import { AppRegistry, ListView,ScrollView,Image,TouchableOpacity,Text, View, StyleSheet } from 'react-native';

//自訂圖片的檔案路徑
var THUMB_URLS = [
require('./imgs/like.png'),
require('./imgs/dislike.png'),
require('./imgs/call.png'),
require('./imgs/fist.png'),
require('./imgs/bandaged.png'),
require('./imgs/flowers.png'),
require('./imgs/heart.png'),
require('./imgs/liking.png'),
require('./imgs/party.png'),
require('./imgs/poke.png'),
require('./imgs/superlike.png'),
require('./imgs/victory.png'),
];

class ListViewDemo extends Component {

constructor(props) {
super(props);
const ds = new ListView.DataSource({
    rowHasChanged: (r1, r2) => r1 !== r2,
    sectionHeaderHasChanged: (s1, s2) => s1 !== s2
});
this.state = {
dataSource: ds.cloneWithRowsAndSections(this.getRows()), //取得getRows()繫結資料
};
}
getRows(){
  let dataObj = {}
  let section = '测試1'
  dataObj[section] = []
  for (let i=0;i<10;i++){
     let data = {
     name:''+i+'',
     num:i
  }
   dataObj[section][i] = data
  }
  section = '测試2'
  dataObj[section] = []
  for (let i=0;i<10;i++){
     let data = {
     name:''+i+'',
     num:i
   }
  dataObj[section][i] = data
 }
 return dataObj
}
//rowData傳入的資料列(每筆資料物件,分組ID, 資列列索引, 是否選中的狀態)
renderRow(rowData,sectionID,rowID,highlightRow){
var imgSource = THUMB_URLS[rowID];
return (
  <View style={styles.rowItem}>
    <View style={styles.rowItemLeft}>
        <Image style={styles.thumb} source={imgSource} resizeMode={"cover"} />
    </View>
    <View style={styles.rowItemRight}>
        <Text style={styles.rowItemText}>{rowData.num}</Text>  
    </View>
  </View>
  )
}
onEndReached(e){
//當資料列執行最後一列的時候,執行此區塊
}
renderSectionHeader(sectionData, sectionID){ //用來顯示資料列的分組的標題(一維陣列的值)
return(
<View style={styles.rowTite}>
    <Text>{sectionID}</Text>
</View>
)
}
onChangeVisibleRows(visibleRows, changedRows){
//當資料列有變化時,呼叫此區塊
}
render() {
return (
<ListView
   style={styles.body}
   onEndReached = {this.onEndReached}
   onEndReachedThreshold = {20}
   renderSectionHeader = {this.renderSectionHeader}
   onChangeVisibleRows = {this.onChangeVisibleRows}
   dataSource={this.state.dataSource}
   renderRow={this.renderRow} />
   );
  }
}

var styles = StyleSheet.create({
body:{
flex:1,
},
rowItem:{
    flex:1,
    height:50,
    flexDirection:'row',
    justifyContent:'center',
    alignItems:'center',
    borderBottomWidth:1,
    borderBottomColor:'#ddd',
},
rowTite:{
    height:30,
    alignItems:'center',
    justifyContent:'center',
    backgroundColor:'#ccc',
},
rowItemLeft:{
   flex:1,
   borderRightColor:'#ccc',
},
rowItemRight:{
  flex:3,
},
rowItemText:{
  textAlign:'center'
},
thumb: {
  width: 30,
  height:30
},
});
module.exports = ListViewDemo;

RenderRow(rowData,sectionID,rowID,highlightRow)
rowData : 每筆資料列視為物件,可以呼叫出每個物件的屬性 ex: rowData.name
sectionID : 分組的ID
rowID : 顯示資料列的索引值,從0開始
highlightRow : 是否該資料列被選中



reactNative 的相關指令:

執行Android 的compile : react-native run-android
建議安裝Android 的模擬器,因為作者無法實機連線到react native ,搞了很久…


後來只好將 apk 安裝在實機上面測試  …  搜尋 \專案路徑 \android\app\build\outputs\apk\ app-debug.apk 透過網路 或USB直接安裝在手機上

最後文件中找到這樣一篇文章

A common issue is that the packager is not started automatically when you run react-native run-android. You can start it manually using react-native start.

下指令 : react-native start   (開始預設PORT 8081)



然後啟動APP就成功囉~謝謝各位


三、參考文獻