React Native已經(jīng)推出好幾年,越來越多的app在使用該技術(shù)進行項目的開發(fā),而今年3月份安卓陣營的快應(yīng)用的推出,讓大前端的理念可能隨之被提出,隨著React Native的越來越成熟,以及快應(yīng)用的出現(xiàn),我們是選擇還是放棄呢?這就要根據(jù)公司的選擇了,但不管怎么我們還是有必要了解一下。
React Native (簡稱RN)是Facebook于2015年4月開源的跨平臺移動應(yīng)用開發(fā)框架,是Facebook早先開源的UI框架 React 在原生移動應(yīng)用平臺的衍生產(chǎn)物,目前支持iOS和安卓兩大平臺。RN使用Javascript語言,類似于HTML的JSX,以及CSS來開發(fā)移動應(yīng)用,因此熟悉Web前端開發(fā)的技術(shù)人員只需很少的學(xué)習(xí)就可以進入移動應(yīng)用開發(fā)領(lǐng)域。React Native使你能夠在Javascript和React的基礎(chǔ)上獲得完全一致的開發(fā)體驗,構(gòu)建世界一流的原生APP。
來來來,開始我們的RN之旅吧!
一、環(huán)境安裝
這個不多說了,根據(jù)RN官方文檔來吧,鏈接地址如下https://reactnative.cn/docs/0.44/getting-started.html
注意:
這里有些需要注意下,按照上面的步驟執(zhí)行下來,安裝的是RN最新的版本,目前是5.1 init命令默認會創(chuàng)建最新的版本,而目前最新的0.45及以上版本需要下載boost等幾個第三方庫編譯。這些庫在國內(nèi)即便翻墻也很難下載成功,導(dǎo)致很多人無法運行iOS項目?。?!中文網(wǎng)在論壇中提供了這些庫的國內(nèi)下載鏈接。如果你嫌麻煩,又沒有對新版本的需求,那么可以暫時創(chuàng)建'0.44.3'的版本。
react-native init JJ1 --version 0.44.3
cd JJ1
react-native run-ios
到這里,我相信你RN的環(huán)境應(yīng)該搞定,開始你的第一個項目了!
二、項目開發(fā)
1.準備工作:
在開始新項目之前,我們首先要了解 ---- 組件的生命周期(相當于OC中的控制器的聲明周期)
先來一張圖吧!

var List = React.createClass({
//1.創(chuàng)建階段
getDefaultProps:function(){
//在創(chuàng)建類的時候被調(diào)用 默認props設(shè)置
console.log('getDefaultProps');
},
//2.實例化階段
getInitialState:function(){
//獲取this.state的默認值
console.log('getInitialState');
return {};
},
componentWillMount:function(){
//在render之前調(diào)用該方法
//業(yè)務(wù)邏輯的處理應(yīng)該放在這里,如對state的操作等
console.log('componentWillMount');
},
render:function(){
//渲染并返回一個虛擬的DOM
console.log('render');
return ( <View > <Text >厲害了,我的國</Text></View>
);
},
componentDidMount:function(){
//該方法發(fā)生在render方法之后,在該方法中,ReactJS會使用render方法返回的虛擬DOM對象來創(chuàng)建真實的DOM結(jié)構(gòu)
console.log('componentDidMount');
},
//更新階段
componentWillReceiveProps:function(){
//該方法發(fā)生在this.props被修改或者父組件調(diào)用了setProps()方法之后
console.log('componentWillReceiveProps');
},
shouldComponentUpdate:function(){
//是否需要更新
console.log('shouldComponentUpdate');
return true;
},
componentWillUpdate:function(){
//將要更新
console.log('componentWillUpdate');
},
componentDidUpdate:function(){
//更新完畢
console.log('componentDidUpdate');
},
//4.銷毀階段
componentWillUnmount:function(){
//銷毀時被調(diào)用,定時器要在這里注銷
console.log('componentWillUnmount');
}
});
對組件的生命周期有一定了解,我們就開始我們的項目啦!
2.項目實踐
項目一般是是由一個新特性頁面或者引導(dǎo)頁開始的,那我們第一件事就是開始我們的引導(dǎo)頁創(chuàng)建
通過ScrollView組件實現(xiàn)輪播的效果,實現(xiàn)代碼如下
import React, { Component } from 'react';
// var Header = require('./Header');
import {
AppRegistry,
Dimensions,
PixelRatio,
View,
ScrollView,
Text,
NavigatorIOS,
Image,
StyleSheet,
TouchableOpacity
} from 'react-native';
import Util from './Views/Common/util';
import App from './app';
module.exports=React.createClass({
render() {
return (
<NavigatorIOS style={{flex:1}}
initialRoute={{
title:'',
component:LaunchView,
navigationBarHidden:true,
}}
renderScene ={this._renderScene} ></NavigatorIOS>
);
},
_renderScene(route,navigator){
return (
<route.component navigator={navigator} {...route} />
)
}
});
var LaunchView = React.createClass({
getInitialState:function(){
return {
data:['iPhone se_b1','iPhone se_b2','iPhone se_b3','iPhone se_b4'],
currentPage:0
};
},
render:function(){
return (<View style={{flex:1}}><ScrollView
ref='scrollView'
horizontal={true}
//當值為true時顯示滾動條
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
//當值為true時,滾動條會停在滾動視圖的尺寸的整數(shù)倍位置。這個可以用在水平分頁上
pagingEnabled={true}
alwaysBounceHorizontal ={false}
alwaysBounceVertical={false}
bounces ={false}
automaticallyAdjustContentInsets={false}
// contentContainerStyle={styles.scroll}
onMomentumScrollEnd = {(e)=>this._onAnimationEnd(e)}
>
{this._launchScrollWithData()}
</ScrollView>
<View style={styles.pageViewStyle}>
{this._renderAllIndicator()}
</View>
</View>);
},
_launchScrollWithData(){
var imgs =[];
let data=this.state.data;
var show =false;
for (let i = 0; i < data.length; i++) {
var url = data[i];
if (i === data.length-1 ) {
show = true;
}
imgs.push(<LaunchItem key={i} num={i} url={url} show={show} {...this.props}/>);
}
return imgs;
},
/**2.手動滑動分頁實現(xiàn) */
_onAnimationEnd:function(e) {
//求出偏移量
let offsetX = e.nativeEvent.contentOffset.x;
//求出當前頁數(shù)
let pageIndex = Math.floor(offsetX / Util.size.width);
//更改狀態(tài)機
this.setState({ currentPage: pageIndex });
},
/**3.頁面指針實現(xiàn) */
_renderAllIndicator() {
let indicatorArr = [];
let style;
let imgsArr = this.state.data;
for (let i = 0; i < imgsArr.length; i++) {
//判斷
style = (i==this.state.currentPage)?{color:'#666'}:{color:'#ddd'};
indicatorArr.push(
<Text key={i} style={[{fontSize:30},style]}>
•
</Text>
);
}
return indicatorArr;
}
});
var LaunchItem=React.createClass({
render:function(){
var num = this.props.num;
var url = this.props.url;
var show = this.props.show;
return (<View style={[styles.img]}>
<View style={styles.img}>
<Image style={styles.img} source={{uri:url}}>
{show ? <TouchableOpacity style={styles.btn} onPress={this.btnDidClick}><View ><Image style={styles.btnContent} source ={{uri:'btn_op'}}></Image></View></TouchableOpacity>
:<View/>}
</Image>
</View>
</View>);
},
btnDidClick:function(){
// console.log('我被點擊了');
this.props.navigator.replace({
component:App
});
}
});
var styles = StyleSheet.create({
img:{
width:Util.size.width,
height:Util.size.height
},
btn:{
width:120,
height:40,
marginLeft:(Util.size.width -120)/2,
marginTop: Util.size.height -150,
// alignSelf: 'center',
// justifyContent:'center',
// alignItems:'center'
},
btnContent:{
backgroundColor:'#3FA9C4',
borderRadius:10,
width:120,
height:40,
},
title:{
fontSize:16,
color:'#fff',
justifyContent:'center',
alignItems:'center',
},
pageViewStyle:{
height:25,
width:Util.size.width,
backgroundColor:'rgba(0,0,0,0)',
position:'absolute',
bottom:20,
flexDirection:'row',
alignItems:'center',
justifyContent:'center'
}
});
一般引導(dǎo)頁的顯示只是第一次打開才會顯示,程序再次打開的時候我們就直接到首頁,或者其他的指定頁面,這里我們需要通過AsyncStorage做一些簡單的存儲。AsyncStorage是一個簡單的,具有異步特性的鍵值對的存儲系統(tǒng),相對這個app而言,它是全局的。
因為AsyncStorage是異步的,所以在使用AsyncStorage的時候,會出現(xiàn)一段時間的白屏現(xiàn)在,我們這里先顯示一張圖片做類似啟動頁的效果,在程序讀取到數(shù)據(jù)的時候,替換它的路由特性就好。

實現(xiàn)代碼如下:
export default class JJ1 extends Component {
render() {
return (
<NavigatorIOS style={{flex:1}}
initialRoute={{
title:'',
component:LaunchImg,
navigationBarHidden:true,
}}
renderScene ={this._renderScene} ></NavigatorIOS>
);
}
_renderScene(route,navigator){
return (
<route.component navigator={navigator} {...route} />
)
}
}
var LaunchImg = React.createClass({
render() {
return (
<View style = {{flex:1}} >
<Image source={{uri:'start_se'}} style={{width:Util.size.width,height:Util.size.height}}></Image>
</View>
);
},
componentDidMount:function(){
this.timer = setTimeout(this.openApp(),2000);
},
openApp(){
AsyncStorage.getItem('isFirstLaunch',(error,result)=>{
if (result === 'false') {
console.log('不是第一次打開');
this.props.navigator.replace({
component:App
});
}else{
console.log('第一次打開');
AsyncStorage.setItem('isFirstLaunch','false',(error)=>{
if (error) {
alert(error);
}
});
this.props.navigator.replace({
component:Intro
});
}
});
},
componentWillUnmount() {
// 如果存在this.timer,則使用clearTimeout清空。
// 如果你使用多個timer,那么用多個變量,或者用個數(shù)組來保存引用,然后逐個clear
this.timer && clearTimeout(this.timer);
}
});
到這里引導(dǎo)頁就已經(jīng)完成,下面我們就創(chuàng)建TabBar,這里可以自定義TabBar從而實現(xiàn)iOS 和安卓能夠同時使用,這里偷懶了,直接使用TabBarIOS去實現(xiàn)了!
實現(xiàn)代碼如下:
module.exports = React.createClass({
getInitialState:function(){
return {
selectedTab:'圖書'
};
},
render:function(){
return(<TabBarIOS>
<TabBarIOS.Item
title = "圖書"
selected={this.state.selectedTab === '圖書'}
icon = {require('./images/book.png')}
onPress ={()=>{
this.setState({
selectedTab:'圖書'
});
}}>
<Book/>
</TabBarIOS.Item>
<TabBarIOS.Item
title="電影"
selected = {this.state.selectedTab ==='電影'}
icon = {require('./images/movie.png')}
onPress = {()=>{
this.setState({
selectedTab:'電影'
});
}}>
<Movie />
</TabBarIOS.Item>
<TabBarIOS.Item
title="音樂"
selected = {this.state.selectedTab === '音樂'}
icon = {require('./images/music.png')}
onPress = {()=>{
this.setState({
selectedTab:'音樂'
});
}}>
<Music/>
</TabBarIOS.Item>
</TabBarIOS>);
}
});
寫到這里,基本的頁面搭建就有了!
剩下就是對一些組件的學(xué)習(xí),ListView實現(xiàn)iOS中TableView 的功能
例如我在圖書頁面通過ListView實現(xiàn)了列表展示,
以及在電影頁面通過ListView實現(xiàn)九宮格的功能,這里類似iOS 中CollectionVIew
以及寫了個簡單的登錄界面
這里頁面還沒有完全寫完,還在持續(xù)擼代碼中...

下面貼出來圖書頁的代碼:
module.exports = React.createClass({
render:function(){
return(<NavigatorIOS
style={{flex:1}}
initialRoute={{
component:BookListView,
title:'圖書',
passProps:{},
}}
renderScene ={this._renderScene} ></NavigatorIOS>
);
},
goToLogin:function(){
this.props.navigator.push({
component:Login,
title:'登錄',
});
},
_renderScene(route,navigator){
return (
<route.component navigator={navigator} {...route} />
)
}
});
var BookListView = React.createClass({
getInitialState:function(){
var ds = new ListView.DataSource({rowHasChanged:(r1,r2)=>r1!==r2});
return{
dataSource:ds.cloneWithRows([]),
keywords:'c語言',
show:false
};
},
render:function(){
return(
<ScrollView style={[styles.flex,styles.containerTop]}>
<View><Search></Search></View>
{this.state.show?
<ListView dataSource={this.state.dataSource}
renderRow={this._renderRow}
renderHeader={this._headerView}
/>:Util.loading}
</ScrollView>);
},
componentDidMount:function(){
this.getData();
},
//渲染圖書列表
_renderRow:function(row,sectionID,rowId){
return(<BookItem row={row} onPress={this.goToNext.bind(this,row.id,rowId)}></BookItem>);
},
_changeText:function(val){
this.setState({
keywords:val
});
},
_search:function(){
this.getData();
},
//根據(jù)關(guān)鍵字查詢
getData:function(){
var ds = new ListView.DataSource({rowHasChanged:(r1,r2)=>r1!==r2});
var that = this;
var baseUrl = ServiceURL.book_search+'?count=10&q='+this.state.keywords;
//開啟loading
this.setState({
show:false
});
//get獲取數(shù)據(jù)
Util.get(baseUrl,function(data){
if (!data.books || !data.books.length) {
return alert('圖書服務(wù)出錯');
}
var books = data.books;
that.setState({
dataSource:ds.cloneWithRows(books),
show:true
});
},function(err){
alert(err);
});
},
goToNext:function(id,rowId){
console.log(rowId);
if (rowId === '0') {
this.props.navigator.push({
component:Login,
title:'登錄',
});
}else{
this.props.navigator.push({
component:BookDetail,
title:id,
passProps:{id:id},
});
}
},
_headerView:function(){
return (<View style={{height:40},{alignItems:'center'},
{flexDirection:'row'}}>
<Text style={{marginLeft:15}}>圖書表頭</Text></View>);
}
});

下面就不在貼電影和登錄頁的代碼了,直接給碼云的git的地址:https://gitee.com/lumic/SouApp.git
代碼比較亂,里面很多是一些實踐的東西,只是作為自己學(xué)習(xí)的記錄。