前言
本篇文章的作用在于幫助你快速上手使用React Native編寫iOS應(yīng)用。如果你現(xiàn)在還不太了解React Native是什么以及Facebook為什么要創(chuàng)建React Native,你可以先看看這篇博客。
閱讀本文之前,我們假設(shè)你已經(jīng)有過使用React創(chuàng)建網(wǎng)站的經(jīng)驗(yàn)。如果你還是一個React新手,那么我們建議你從React的網(wǎng)站開始學(xué)習(xí)。
設(shè)置
使用React Native開發(fā)iOS應(yīng)用需要OSX系統(tǒng),Xcode,Homebrew,node,npm以及watchman,你也可以有選擇的使用Flow。
在安裝完這些依賴項(xiàng)目之后,你可以簡單的使用兩行命令來開啟一個React Native項(xiàng)目:
npm install -g react-native-cli
react-native-cli是用來開發(fā)React Native的命令行工具。你需要使用npm來安裝它。上面這行代碼將會幫助你在terminal中安裝react-native命令。當(dāng)然,你只需要運(yùn)行一次這行代碼。
react-native init AwsomeProject
這行代碼可以獲取所有React Native的源碼以及依賴項(xiàng),同時會創(chuàng)建一個叫做AwsomeProject/AwsomeProject.xcodeproj的全新Xcode項(xiàng)目。
開發(fā)
現(xiàn)在你可以在Xcode中開發(fā)這個新項(xiàng)目(AwsomeProject/AwsomeProject.xcodeproj),并簡單的使用cmd+R來運(yùn)行它。運(yùn)行代碼的同時也會自動開啟一個node服務(wù)器來實(shí)現(xiàn)代碼的熱重載。這樣一來你就可以通過cmd+R來查看變化而不需要每次都在Xcode中進(jìn)行重編譯。
在本文中我們將創(chuàng)建一個簡單的電影應(yīng)用,這個應(yīng)用將抓取目前正在上映的最新的25部電影,并將它們展示在一個ListView中。
Hello World
react-native init會復(fù)制Example/SampleProject中的內(nèi)容到你命名的項(xiàng)目中,在本文中項(xiàng)目名稱為AwsomeProject。這是一個簡單的hello world應(yīng)用。你可以通過編輯index.os.js來改變這個應(yīng)用,然后使用cmd+R在模擬器中查看變化。
偽造數(shù)據(jù)
在我們開始編寫代碼從Rotten Tomatoes網(wǎng)站抓取數(shù)據(jù)之前,我們先來偽造一些數(shù)據(jù)以便我們可以馬上體驗(yàn)一下React Native。在Facebook我們一般會在JS文件的頂部聲明常量,并在后面使用,但是隨便你加在哪里都好。在index.ios.js中添加以下代碼:
var MOCKED_**_DATA = [ {title: 'Title', year: '2015', posters: {thumbnail: 'http://i.imgur.com/UePbdph.jpg'}}, ];
渲染一部電影
我們會渲染電影標(biāo)題,年份以及電影海報略縮圖。由于略縮圖在React Native中是一個Image組件,我們需要將Imagei到React的依賴項(xiàng)中。
var { AppRegistry, Image, StyleSheet, Text, View, } = React;
現(xiàn)在我們修改render函數(shù)以便我們可以將上面渲染上面的數(shù)據(jù)而不僅僅是渲染一個hello world:
render: function() { var movie = MOCKED_**_DATA[0]; return (); } {movie.title} {movie.year} ![]()
按下cmd+R你應(yīng)該在”2015”上面看到”Title”。注意此時Image什么都不會渲染。這是因?yàn)槲覀冞€沒有指定想要的寬度和高度。這需要通過styles屬性來設(shè)置。在我們修改styles的同時我們還需要把那些不再會使用的樣式刪除:
var styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, thumbnail: { width: 53, height: 81, }, });
最后我們需要將樣式運(yùn)用在Image組件上。
按下cmd+R你會發(fā)現(xiàn)圖片已經(jīng)渲染出來了。

添加其他樣式
很好,我們現(xiàn)在已經(jīng)把數(shù)據(jù)渲染出來了。現(xiàn)在我們來讓我們的應(yīng)用變得好看一些。我想把文字放在圖片的右側(cè),同時讓標(biāo)題大一些并居中:
+---------------------------------+ |+-------++----------------------+| || || Title || || Image || || || || Year || |+-------++----------------------+| +---------------------------------+
我們會添加另一個container,這是為了讓我們的組件在外層的組件中垂直居中。
return (); ![]()
{movie.title} {movie.year}
現(xiàn)在并沒有多少變化,我們在文字外層添加了一個包裹容器并將其放在了圖片后面(因?yàn)槲淖忠趫D片的右邊)。現(xiàn)在我們來看看樣式會變成什么樣:
container: { flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', },
我們在這里使用彈性盒模型來布局,如果你不熟悉彈性盒模型,可以看看這個教程。
在上面的代碼中,我們簡單的添加了flexDirection: 'row'來確保我們的main container是水平布局而不是垂直布局。
現(xiàn)在我們添加另一組樣式:
rightContainer: { flex: 1, },
上面代碼的意思是rightContainer會占據(jù)外層容器右邊的空間,左邊則是圖片。如果沒有看出效果,可以為rightContainer添加一個backgroundColor屬性,同時移除flex: 1。你會看到外出容器的體積會變得勁量的小來適應(yīng)子容器。
而文本的樣式很直觀:
title: { fontSize: 20, marginBottom: 8, textAlign: 'center', }, year: { textAlign: 'center', },
繼續(xù)按下cmd+R來查看更新之后的視圖:

抓取真實(shí)數(shù)據(jù)
從Rotten Tomatoes的API抓取數(shù)據(jù)和學(xué)習(xí)React Native并沒有多少關(guān)系,所以你可以風(fēng)輕云淡的跳過這一節(jié)。
將下面的常量放在文件的頂部來創(chuàng)建一個請求數(shù)據(jù)使用的REQUEST_URL:
var API_KEY = '7waqfqbprs7pajbz28mqf6vz'; var API_URL = 'http://api.rottentomatoes.com/api/public/v1.0/lists/**in_theaters.json'; var PAGE_SIZE = 25; var PARAMS = '?apikey=' + API_KEY + '&page_limit=' + PAGE_SIZE; var REQUEST_URL = API_URL + PARAMS;
為我們的應(yīng)用添加初始狀態(tài)以便我們可以通過檢查this.state.** === null來確定電影數(shù)據(jù)有沒有被城管加載。當(dāng)電影數(shù)據(jù)返回時,我們可以通過this.setState({**: **Data})來設(shè)置數(shù)據(jù)。將下面的代碼添加到render函數(shù)之前:
getInitialState: function() { return { **: null, }; },
我們想要在組件完成加載后發(fā)送請求,componentDidMount是React組件中的一個函數(shù),它只會在組件加載完成之后被調(diào)用一次。
componentDidMount: function() { this.fetchData(); },
現(xiàn)在添加組件中會用到的fetchData函數(shù)。這個方法將負(fù)責(zé)處理數(shù)據(jù)抓取。你需要做的僅僅是在promise完成解析之后調(diào)用this.setState({**: data}),因?yàn)閟etState會觸發(fā)重新渲染,而此時render函數(shù)會注意到this.state.**不再是null。注意我們會在promise鏈的最后調(diào)用done()–一定要確保調(diào)用done(),否則錯誤信息可能會被忽略。
fetchData: function() { fetch(REQUEST_URL) .then((response) => response.json()) .then((responseData) => { this.setState({ **: responseData.**, }); }) .done(); },
現(xiàn)在修改render函數(shù)來渲染一個loading視圖,如果電影數(shù)據(jù)還沒有返回的話,否則將渲染第一部電影:
render: function() { if (!this.state.**) { return this.renderLoadingView(); } var movie = this.state.**[0]; return this.renderMovie(movie); }, renderLoadingView: function() { return (); }, renderMovie: function(movie) { return ( Loading **... ); }, ![]()
{movie.title} {movie.year}
現(xiàn)在按下cmd+R,你應(yīng)該已經(jīng)看到了”Loading **…”,直到電影數(shù)據(jù)返回,接著頁面就會渲染第一部從Rotten Tomatoes抓回來的電影:

ListView
現(xiàn)在我們來修改應(yīng)用來將所有的數(shù)據(jù)渲染在一個ListView組件種,而不是只渲染一部電影。
為什么使用ListView要比把所有數(shù)據(jù)放在一個ScrollView里面好呢?雖然React速度很快,但是渲染一個可能是無限長的列表依然可能很慢。ListView會自動渲染視線之內(nèi)的視圖,而那些在屏幕之外的視圖會被暫時移除。
第一件事:在文件的最上方添加ListView:
var { AppRegistry, Image, ListView, StyleSheet, Text, View, } = React;
現(xiàn)在修改render函數(shù)以便一旦我們的數(shù)據(jù)返回沃恩就可以在一個ListView里面渲染數(shù)據(jù):
render: function() { if (!this.state.loaded) { return this.renderLoadingView(); } return (); }
DataSource是一個ListView的接口,作用是決定那些行會被改變。
注意在這里使用dataSource而不是this.state。下一步我們需要在getInitialState的返回對象上添加一個空的dataSource,我們不能再使用this.state.**防止數(shù)據(jù)被存儲兩次。我們可以使用state的布爾值屬性(this.state.loaded)來判斷數(shù)據(jù)抓取是否結(jié)束:
getInitialState: function() { return { dataSource: new ListView.DataSource({ rowHasChanged: (row1, row2) => row1 !== row2, }), loaded: false, }; },
在這里我們還需要修改fetchData方法來更新state:
fetchData: function() { fetch(REQUEST_URL) .then((response) => response.json()) .then((responseData) => { this.setState({ dataSource: this.state.dataSource.cloneWithRows(responseData.**), loaded: true, }); }) .done(); },
最后,我們在styles中為ListView組件添加樣式:
listView: { paddingTop: 20, backgroundColor: '#F5FCFF', },
下面是最終的效果圖:

接下來我們還可以通過添加導(dǎo)航,搜索,無線滾動加載等等來彎沉一個完整的應(yīng)用。你可以查看[電影示例](** Example)來查看完整的代碼。
完整的源碼
/** * Sample React Native App * https://github.com/facebook/react-native */ 'use strict'; var React = require('react-native'); var { AppRegistry, Image, ListView, StyleSheet, Text, View, } = React; var API_KEY = '7waqfqbprs7pajbz28mqf6vz'; var API_URL = 'http://api.rottentomatoes.com/api/public/v1.0/lists/**in_theaters.json'; var PAGE_SIZE = 25; var PARAMS = '?apikey=' + API_KEY + '&page_limit=' + PAGE_SIZE; var REQUEST_URL = API_URL + PARAMS; var AwesomeProject = React.createClass({ getInitialState: function() { return { dataSource: new ListView.DataSource({ rowHasChanged: (row1, row2) => row1 !== row2, }), loaded: false, }; }, componentDidMount: function() { this.fetchData(); }, fetchData: function() { fetch(REQUEST_URL) .then((response) => response.json()) .then((responseData) => { this.setState({ dataSource: this.state.dataSource.cloneWithRows(responseData.**), loaded: true, }); }) .done(); }, render: function() { if (!this.state.loaded) { return this.renderLoadingView(); } return (); }, renderLoadingView: function() { return ( ); }, renderMovie: function(movie) { return ( Loading **... ); }, }); var styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, rightContainer: { flex: 1, }, title: { fontSize: 20, marginBottom: 8, textAlign: 'center', }, year: { textAlign: 'center', }, thumbnail: { width: 53, height: 81, }, listView: { paddingTop: 20, backgroundColor: '#F5FCFF', }, }); AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject); ![]()
{movie.title} {movie.year}
來源http://facebook.github.io/react-native/docs/tutorial.htm