React Native入門-實戰解析(上)
來自Leo的原創部落格,轉載請著名出處
概述
隨著app端越來越複雜,迭代越來越快,很多app採用原生+html5的方式來實現,然後不知道什麼時候,它就有了個高大上的名字 - hybrid app。類似的框架也很多,比較有名的有
這種app的原理是,用webview來實現類似原生的介面,也就是用h5寫的程式碼是執行在webview裡的。優點很明顯
- 動態部署(不需要每次通過應用商店的稽核,尤其是iOS,稽核有時候真的覺得兩顆蛋不夠疼的。)
- 介面靈活(用h5來寫的介面要比原生程式碼靈活的多)
- 開發迅速(一套程式碼,iOS,安卓都可以跑;不管是安卓還是iOS,用原生的程式碼實現類似webview的頁面,佈局什麼的都是很複雜的一件事情)
同樣,缺點也很明顯
- 效能相對較差(這一點在安卓上尤其明顯,安卓的webview效能真的很差,而且安卓機型較多)
- 資料處理能力較差,跑在webview裡的程式,而且JavaScript是單執行緒的,所以很難利用好多核多執行緒,
- 和硬體組建的互動較差,例如相簿,藍芽,motion等。
React Native
React native是facebook公司推出的一個app開發框架,facebook也有很多其他的框架,在這裡都可以找到。
使用純react native開發的時候,實際的開發語言是JavaScript和框架React。寫一套程式碼,同時執行在多個平臺上。
和上文提到的hybrid app最大的區別是
用React Native開發的時候,實際執行的都是原生的程式碼
目前React Native支援
- >=Android 4.1
- >= iOS 7.0
它的優點
- 可以動態部署,因為是用JS寫的,並不需要編譯,所以可以在執行期動態的從伺服器下載,並且執行。
- 開發期間,修改介面的時候,只需要Command+R來重新整理即可,不需要每次修改都蛋疼的重新編譯
- 支援和原生程式碼混合程式設計,所以,對於那些效能要求較高,資料處理複雜的頁面,仍然可以用原生的程式碼來實現。
如果你沒有任何的JS和React基礎
可以先看看,我之前的這篇JS和React的基礎文章
本文最終的效果
本文的目的是實現一個從網路獲取資料,載入到ListView,然後點選某一行可以跳轉到詳情頁。
React Native環境搭建
由於本文側重的是如何使用React Native進行開發,所以並不會詳細講解如何安裝和搭建環境。可以參考官方文件,搭建很簡單
有幾點提一下
- 最好是Mac電腦,OS X系統,因為iOS執行環境需要OSX
- iOS目前需要XCode 7 +
- 安卓需要Android SDK,和模擬器
文件
關於React Native的文件,在這裡你都可以找到,這個系列我不會翻譯facebook的文件。能閱讀英文文件是程式設計師的一項基本技能,但是我會在使用的時候簡單提一下
建立一個工程
開啟終端,cd到想要的目錄去,然後
react-native init LeoRNWeather
可以看到生成了一個LeoRNWeather的資料夾,這個資料夾的預設的檔案如下
android //安卓的工程
index.ios.js //iOS的程式入口檔案
node_modules //
index.android.js //安卓的入口檔案
ios //iOS的工程
package.json //全域性的描述資訊,本文就使用預設的了
對了我使用的IDE,是Atom
然後,可以手動開啟 ios
目錄下的XCode 工程,然後點選執行,如果能見到下面截圖,代表執行成功
入門
記住,React Native沒有CSS,所有的實現都是JS的語法。當你開啟index.ios.js
的時候,大概能發現幾個模組
匯入的模組,要先匯入才能使用
import React, {
****
} from 'react-native';
樣式佈局定義,用JS的語法,由StyleSheet建立,其中樣式使用了React的FlexBox,讓佈局變的十分簡單
const styles = StyleSheet.create({
//*
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
//*
});
檢視元件,檢視繼承自Component,可以在文件上找到很多Components
class LeoRNWeather extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
</View>
);
}
}
可以看看這一行,可以看到React的檢視語法和H5類似,標準的XML格式。
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
!!!!我們刪除這個檔案裡的全部內容,然後替換成React的風格程式碼
這時候程式碼如下
import React, {
AppRegistry,
Component,
StyleSheet,
View,
ListView,
Text,
} from 'react-native';
var LeoRNWeather = React.createClass({
render(){
return (
<View style= {styles.container}>
<Text style={styles.blackText}>這是一個標題</Text>
</View>
);
}
});
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
justifyContent: 'center',
},
blackText:{
fontSize:20,
color:'rgb(0,0,0)',
backgroundColor:'rgba(255,255,255,0)',
textAlign:'center',
marginLeft:10,
},
});
AppRegistry.registerComponent('LeoRNWeather', () => LeoRNWeather);
效果
新增導航欄
這裡提一下,在React Native中,導航欄有兩種
Navigator
,大部分的情況下使用這個,由facebook的react native團隊進行開發,一直在維護,同時支援iOS和安卓,由於在導航切換的時候需要進行大量的載入,所以會佔用JS執行緒較多時間。NavigatorIOS
,很少使用,由開源社群開發,有很多bug,僅僅支援iOS。但是內部由原生的UINavigationController實現,所以實際執行的時候,和原生的iOS導航一樣,有一樣的動畫
本文使用NavigatorIOS
,react native的相關資料還不是很多,一定要會看英文文件,NavigationIOS
的文件可以在這裡找到
在頂部import引入
NavigatorIOS,
然後,重寫LeoRNWeather
,增加導航欄,其中
- initialRoute 定義最初的頁面,類似iOS中的
rootViewController
,title表示標題,component表示渲染的物件,是Component的子類
var LeoRNWeather = React.createClass({
render: function() {
return (
<NavigatorIOS
style={styles.container}
initialRoute={{
title: '主頁',
component: ListScreen,
}}
/>
);
}
});
建立ListScreen
var ListScreen = React.createClass({
render(){
return (
<View style= {styles.container}>
<Text style={styles.blackText}>blog.csdn.net/hello_hwc</Text>
</View>
);
}
});
然後, Save,選擇模擬器,command+R重新整理,可以看到效果(修改了文字)
新增背景圖
首先,在目錄裡新增一張圖片
Tips:當向iOS工程中Images.xcassets新增了圖片或者android新增了res/drawable新增圖片的時候,需要重新編譯
然後,將index.ios.js
修改成如下
import React, {
AppRegistry,
Component,
StyleSheet,
View,
ListView,
Text,
NavigatorIOS,
Image,
} from 'react-native';
var ListScreen = React.createClass({
render(){
return (
<Image source={require('./img/background.png')} style={styles.backgroundImg}>
<Text style={styles.whiteText}>blog.csdn.net/hello_hwc</Text>
</Image>
);
}
});
var LeoRNWeather = React.createClass({
render: function() {
return (
<NavigatorIOS
style={styles.container}
initialRoute={{
title: '主頁',
component: ListScreen,
}}
/>
);
}
});
const styles = StyleSheet.create({
backgroundImg:{
flex:1,
width: null,
height: null,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
whiteText:{
fontSize:20,
color:'rgb(255,255,255)',
backgroundColor:'rgba(255,255,255,0)',
textAlign:'left',
marginLeft:10,
},
container: {
flex: 1,
backgroundColor: 'white',
justifyContent: 'center',
},
blackText:{
fontSize:20,
color:'rgb(0,0,0)',
backgroundColor:'rgba(255,255,255,0)',
textAlign:'center',
marginLeft:10,
},
});
AppRegistry.registerComponent('LeoRNWeather', () => LeoRNWeather);
效果圖
Tips
通過設定flex為1來讓寬度高度填充100%,通過height,width為null,來讓Image填充螢幕
通過設定父檢視的alignItems:'center'
flexDirection:'column'
來設定水平居,alignItems:'center'
flexDirection:'row'
來設定垂直居中
關於Flexbox佈局,可以參考這片文章,寫的非常詳細
進行網路請求
React Native網路請求的文件可以在這裡找到,在React中,網路請求使用Fetch,
例如,你可以這樣去呼叫一個POST請求
fetch('https://mywebsite.com/endpoint/', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
firstParam: 'yourValue',
secondParam: 'yourOtherValue',
})
})
由於網路請求是一個非同步的請求,所以,它返回的是一個Promise物件,對於這個物件,有兩種處理方式
- 同步處理
then
和catch - 非同步處理,
async/await
還有一個API是
XMLHttpRequest
,它是建立在iOS網路請求api之上的,本文不做討論。
由於本文是這個React Native系列的第一篇,所以處理方式採用同步處理。簡單直接。
在類ListScreen
中,新增如下兩個方法
//Component掛載完畢後呼叫
componentDidMount() {
this.fetchData();
},
fetchData() {
fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => {
})
.done();
},
這裡的REQUEST_URL
是一個全域性變數
var REQUEST_URL = 'https://raw.githubusercontent.com/LeoMobileDeveloper/React-Native-Files/master/person.json';
然後,save,command+R重新整理模擬器,會發現Log如下
2016-04-21 13:53:49.563 [info][tid:com.facebook.React.JavaScript] [ { nickname: 'Leo', realname: 'WenchenHuang' },
{ nickname: 'Jack', realname: 'SomethingElse' } ]
為了顯示到ListView中,我們要把網路請求來的資料儲存下來,為ListScreen新增如下方法
- 用loaded來判斷網路資料是否載入完畢
- 用users來儲存實際的的網路資料,這裡因為users是ListView的dataSource,所以用ListView的DataSource來初始化
//自動呼叫一次,用來設定this.state的初始狀態
getInitialState: function() {
return {
loaded: false,
users: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
}),
};
},
然後修改fetchData方法,在載入完畢後儲存資料
fetchData() {
fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => {
this.setState({
users: this.state.users.cloneWithRows(responseData),
loaded: true,
});
})
.done();
},
Tips:this.setState會觸發render重新呼叫,進行重繪
寫出一個列表
移動開發中,列表是一個非常常用的控制元件。(iOS中的Tableview,android中的listview)。
ListView的優點是,當檢視離開螢幕的時候,會被複用或者移除,降低記憶體使用。關於ListVIew,ReactNative團隊進行了很多優化,比如event-loop只渲染一個cell,將渲染工作分成很多個小的碎片執行,來防止掉幀。
如何使用ListView
最少需要以下兩個
dataSource
,一個簡單陣列來描述MVC中的model,類似於iOS中的dataSource
renderRow
,返回一個檢視組建.類似於iOS中的cellForRowAtIndexPath
renderSeparator
,一般也需要這個方法,來說生成一個分隔線
當然,
listView
也支援很多,比如像iOS那樣的section header,header,footer,以及很多的事件回撥,在listView的文件裡,你都可以找到。
這時候的ListScreen類如下
var ListScreen = React.createClass({
getInitialState: function() {
return {
loaded: false,
users: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
}),
};
},
componentDidMount() {
this.fetchData();
},
fetchData() {
fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => {
this.setState({
users: this.state.users.cloneWithRows(responseData),
loaded: true,
});
})
.done();
},
render(){
if (!this.state.loaded) {
return this.renderLoadingView()
}
return this.renderList()
},
renderLoadingView() {
return (
<Image source={require('./img/background.png')} style={styles.backgroundLoading}>
<ActivityIndicatorIOS
style={[styles.centering, {height: 80}]}
size="large"
color="#ffffff"
/>
</Image>
);
},
renderList(){
return (
<Image source={require('./img/background.png')} style={styles.backgroundImg}>
<ListView
dataSource={this.state.users}
renderRow={this.renderRow}
style={styles.fullList}
renderSeparator={(sectionID, rowID) => <View key={`${sectionID}-${rowID}`} style={styles.separator} />}
/>
</Image>
);
},
renderRow(user){
return (
<TouchableHighlight
onPress={() => this.rowClicked(user)}
underlayColor = '#ddd'>
<View style={styles.rightCongtainer}>
<Text style={styles.whiteText}>{user.nickname}</Text>
<Text style={styles.whiteText}>{user.realname}</Text>
</View>
</TouchableHighlight>
);
},
rowClicked(user){
console.log(user);
},
});
Styles如下
const styles = StyleSheet.create({
backgroundImg:{
flex:1,
width: null,
height: null,
flexDirection: 'row'
},
backgroundLoading:{
flex:1,
width: null,
height: null,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row'
},
thumbnail: {
width: 60,
height: 60,
},
rightCongtainer:{
flex:1,
},
fullList:{
flex:1,
paddingTop: 64,
},
separator: {
height: 0.5,
backgroundColor: 'rgba(255,255,255,0.5)',
},
centering: {
alignItems: 'center',
justifyContent: 'center',
},
whiteText:{
fontSize:20,
color:'rgb(255,255,255)',
backgroundColor:'rgba(255,255,255,0)',
textAlign:'left',
marginLeft:10,
},
blackText:{
fontSize:20,
color:'rgb(0,0,0)',
backgroundColor:'rgba(255,255,255,0)',
textAlign:'center',
marginLeft:10,
},
container: {
flex: 1,
backgroundColor: 'white',
justifyContent: 'center',
},
});
這時候,save,command+R後,發現再網路請求的時候會先顯示小菊花轉轉轉,然後載入完畢之後,顯示一個List
載入Spinner(僅適用於iOS)
這個在上面的程式碼中提到了
renderLoadingView() {
return (
<Image source={require('./img/background.png')} style={styles.backgroundLoading}>
<ActivityIndicatorIOS
style={[styles.centering, {height: 80}]} //風格
size="large" //大小
color="#ffffff" //顏色
/>
</Image>
);
},
控制檯列印
上文的程式碼裡提到
rowClicked(user){
console.log(user);
},
使用console.log來實現控制檯
這時候,我們在點選某一行,會看到XCode中輸出
在Chrome中除錯
使用Command+control+Z來調出除錯視窗,然後選擇Debug in chrome
這時候,App會和Chrome建立一個socket連線,這樣在Chrome中就可以進行除錯了。
開啟Chrome開發者工具
點選某一行,就會發現在chrome的控制檯進行log了
新增一個詳情頁,並且傳值
新建一個Component來表示詳情頁
var DetailScreen = React.createClass({
render(){
return (
<View style= {styles.container}>
<Text style={styles.blackText}>{this.props.user.nickname}</Text>
<Text style={styles.blackText}>{this.props.user.realname}</Text>
</View>
);
}
});
然後,在rowClick中,跳轉到詳情頁
rowClicked(user){
console.log(user);
this.props.navigator.push({
title: "詳情頁",
component: DetailScreen,
passProps: {user:user},
});
},
Tips:
- NavigatorIOS可以通過
this.props.navigator
來訪問 - 通過
this.props.navigator.push
來跳轉,通過passProps: {user:user}
來傳遞值 - 每個Component的類都有兩個全獨享,this.props表示引數,this.state表示當前的狀態。可以用來儲存和傳遞資料
簡單提一下React Native的效能
在RN中,主要有兩個執行緒
- JavaScript執行緒
- 主執行緒(UI執行緒)
其中,JavaScript是React Native的JS程式碼執行執行緒,React Native的觸控處理,網路請求,檢視配置,以及app的業務邏輯都是發生在這裡的。主執行緒是實際原生程式碼繪製檢視的執行執行緒。使用React Native的時候,往往會遇到JavaScript執行緒執行邏輯過多,沒有辦法及時響應UI執行緒,導致掉幀.所以,React Native的效能,較純原生的還是要差一些的
後續
React Native實戰解析(中)會繼續講解一些基礎控制元件的適用,然後也會寫一個demo的app,React Native實戰解析(下)會寫一個相對完善點的應用,作為這個入門系列的終結。然後,計劃寫一兩篇混合程式設計的,最近比較忙,這個系列慢慢更新吧
附錄,最終的index.ios.js
全部程式碼
/**
* Sample React Native App
* https://github.com/facebook/react-native
*/
import React, {
AppRegistry,
Component,
StyleSheet,
ListView,
Text,
View,
Image,
ActivityIndicatorIOS,
Navigator,
TouchableHighlight,
TouchableOpacity,
NavigatorIOS,
} from 'react-native';
var REQUEST_URL = 'https://raw.githubusercontent.com/LeoMobileDeveloper/React-Native-Files/master/person.json';
var ListScreen = React.createClass({
getInitialState: function() {
return {
loaded: false,
users: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
}),
};
},
componentDidMount() {
this.fetchData();
},
fetchData() {
fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => {
this.setState({
users: this.state.users.cloneWithRows(responseData),
loaded: true,
});
})
.done();
},
render(){
if (!this.state.loaded) {
return this.renderLoadingView()
}
return this.renderList()
},
renderLoadingView() {
return (
<Image source={require('./img/background.png')} style={styles.backgroundLoading}>
<ActivityIndicatorIOS
style={[styles.centering, {height: 80}]}
size="large"
color="#ffffff"
/>
</Image>
);
},
renderList(){
return (
<Image source={require('./img/background.png')} style={styles.backgroundImg}>
<ListView
dataSource={this.state.users}
renderRow={this.renderRow}
style={styles.fullList}
renderSeparator={(sectionID, rowID) => <View key={`${sectionID}-${rowID}`} style={styles.separator} />}
/>
</Image>
);
},
renderRow(user){
return (
<TouchableHighlight
onPress={() => this.rowClicked(user)}
underlayColor = '#ddd'>
<View style={styles.rightCongtainer}>
<Text style={styles.whiteText}>{user.nickname}</Text>
<Text style={styles.whiteText}>{user.realname}</Text>
</View>
</TouchableHighlight>
);
},
rowClicked(user){
console.log(user);
this.props.navigator.push({
title: "詳情頁",
component: DetailScreen,
passProps: {user:user},
});
},
});
var DetailScreen = React.createClass({
render(){
return (
<View style= {styles.container}>
<Text style={styles.blackText}>{this.props.user.nickname}</Text>
<Text style={styles.blackText}>{this.props.user.realname}</Text>
</View>
);
}
});
var LeoRNWeather = React.createClass({
render: function() {
return (
<NavigatorIOS
style={styles.container}
initialRoute={{
title: '主頁',
component: ListScreen,
}}
/>
);
}
});
const styles = StyleSheet.create({
backgroundImg:{
flex:1,
width: null,
height: null,
flexDirection: 'row'
},
backgroundLoading:{
flex:1,
width: null,
height: null,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row'
},
thumbnail: {
width: 60,
height: 60,
},
rightCongtainer:{
flex:1,
},
fullList:{
flex:1,
paddingTop: 64,
},
separator: {
height: 0.5,
backgroundColor: 'rgba(255,255,255,0.5)',
},
centering: {
alignItems: 'center',
justifyContent: 'center',
},
whiteText:{
fontSize:20,
color:'rgb(255,255,255)',
backgroundColor:'rgba(255,255,255,0)',
textAlign:'left',
marginLeft:10,
},
blackText:{
fontSize:20,
color:'rgb(0,0,0)',
backgroundColor:'rgba(255,255,255,0)',
textAlign:'center',
marginLeft:10,
},
container: {
flex: 1,
backgroundColor: 'white',
justifyContent: 'center',
},
});
AppRegistry.registerComponent('LeoRNWeather', () => LeoRNWeather);
相關文章
- React實戰入門指南React
- React Native入門介紹React Native
- React.js入門與實戰ReactJS
- React Native 從入門到原理React Native
- react-native + mobx 入門到放棄React
- React-native專案入門與思考React
- Pepperoni是React Native的入門起步套件React Native套件
- React Native iOS混合開發實戰教程React NativeiOS
- React Native基礎&入門教程:除錯React Native應用的一小步React Native除錯
- 手把手教你React Native實戰從 React到Rn《二》React Native
- Mac上配置React NativeMacReact Native
- React Native Android混合開發實戰教程React NativeAndroid
- React-Native入門(1)-專案工程初識React
- React-Native入門(3) this、props和state使用React
- 從 Android 到 React Native 開發(一、入門)AndroidReact Native
- react-native 之匯入(import)、匯出(export)深刻解析ReactImportExport
- React Native 中實現動態匯入React Native
- 【原始碼解析】React Native元件渲染原始碼React Native元件
- React Native元件(二)View元件解析React Native元件View
- React-Native入門(2)-簡單闡述跳轉React
- Kafka實戰-入門Kafka
- ElasticSearch實戰-入門Elasticsearch
- podman 入門實戰
- 如何從零入門React?實戰做個FM應用吧React
- Maven實戰入門視訊教程-解析maven多模組管理Maven
- React Native 載入圖片React Native
- 在 React Native 中原生實現動態匯入React Native
- React-Native實踐React
- Flutter For Web入門實戰FlutterWeb
- phoneGap入門實戰
- react入門React
- Python3網路爬蟲快速入門實戰解析Python爬蟲
- 快速入門——深度學習理論解析與實戰應用深度學習
- React Native基礎&入門教程:初步使用Flexbox佈局React NativeFlex
- React Native 入門(三) - 給 Android 開發者的學習建議React NativeAndroid
- react非同步載入元件實現解析React非同步元件
- 5000字的React-native原始碼解析React原始碼
- react-native 啟動流程原理解析React