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

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

抓取真實數據
從Rotten Tomatoes的API抓取數據和學習React Native并沒有多少關系,所以你可以風輕云淡的跳過這一節。
將下面的常量放在文件的頂部來創建一個請求數據使用的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;
為我們的應用添加初始狀態以便我們可以通過檢查this.state.** === null來確定電影數據有沒有被城管加載。當電影數據返回時,我們可以通過this.setState({**: **Data})來設置數據。將下面的代碼添加到render函數之前:
getInitialState: function() { return { **: null, }; },
我們想要在組件完成加載后發送請求,componentDidMount是React組件中的一個函數,它只會在組件加載完成之后被調用一次。
componentDidMount: function() { this.fetchData(); },
現在添加組件中會用到的fetchData函數。這個方法將負責處理數據抓取。你需要做的僅僅是在promise完成解析之后調用this.setState({**: data}),因為setState會觸發重新渲染,而此時render函數會注意到this.state.**不再是null。注意我們會在promise鏈的最后調用done()–一定要確保調用done(),否則錯誤信息可能會被忽略。
fetchData: function() { fetch(REQUEST_URL) .then((response) => response.json()) .then((responseData) => { this.setState({ **: responseData.**, }); }) .done(); },
現在修改render函數來渲染一個loading視圖,如果電影數據還沒有返回的話,否則將渲染第一部電影:
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}
現在按下cmd+R,你應該已經看到了”Loading **…”,直到電影數據返回,接著頁面就會渲染第一部從Rotten Tomatoes抓回來的電影:

ListView
現在我們來修改應用來將所有的數據渲染在一個ListView組件種,而不是只渲染一部電影。
為什么使用ListView要比把所有數據放在一個ScrollView里面好呢?雖然React速度很快,但是渲染一個可能是無限長的列表依然可能很慢。ListView會自動渲染視線之內的視圖,而那些在屏幕之外的視圖會被暫時移除。
第一件事:在文件的最上方添加ListView:
var { AppRegistry, Image, ListView, StyleSheet, Text, View, } = React;
現在修改render函數以便一旦我們的數據返回沃恩就可以在一個ListView里面渲染數據:
render: function() { if (!this.state.loaded) { return this.renderLoadingView(); } return (); }
DataSource是一個ListView的接口,作用是決定那些行會被改變。
注意在這里使用dataSource而不是this.state。下一步我們需要在getInitialState的返回對象上添加一個空的dataSource,我們不能再使用this.state.**防止數據被存儲兩次。我們可以使用state的布爾值屬性(this.state.loaded)來判斷數據抓取是否結束:
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', },
下面是最終的效果圖:

接下來我們還可以通過添加導航,搜索,無線滾動加載等等來彎沉一個完整的應用。你可以查看[電影示例](** 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