前言
本篇文章的作用在于幫助你快速上手使用React Native編寫iOS應(yīng)用访递。如果你現(xiàn)在還不太了解React Native是什么以及Facebook為什么要創(chuàng)建React Native,你可以先看看這篇博客。
閱讀本文之前辛藻,我們假設(shè)你已經(jīng)有過使用React創(chuàng)建網(wǎng)站的經(jīng)驗。如果你還是一個React新手小腊,那么我們建議你從React的網(wǎng)站開始學(xué)習(xí)伏尼。
設(shè)置
使用React Native開發(fā)iOS應(yīng)用需要OSX系統(tǒng),Xcode,Homebrew介蛉,node萌庆,npm以及watchman,你也可以有選擇的使用Flow币旧。
在安裝完這些依賴項目之后践险,你可以簡單的使用兩行命令來開啟一個React Native項目:
npm install -g react-native-cli
react-native-cli是用來開發(fā)React Native的命令行工具。你需要使用npm來安裝它吹菱。上面這行代碼將會幫助你在terminal中安裝react-native命令捏境。當(dāng)然,你只需要運行一次這行代碼毁葱。
react-native init AwsomeProject
這行代碼可以獲取所有React Native的源碼以及依賴項垫言,同時會創(chuàng)建一個叫做
AwsomeProject/AwsomeProject.xcodeproj
的全新Xcode項目。
開發(fā)
現(xiàn)在你可以在Xcode中開發(fā)這個新項目AwsomeProject/AwsomeProject.xcodeproj
倾剿,并簡單的使用cmd+R
來運行它筷频。運行代碼的同時也會自動開啟一個node服務(wù)器來實現(xiàn)代碼的熱重載。這樣一來你就可以通過cmd+R
來查看變化而不需要每次都在Xcode中進行重編譯前痘。
在本文中我們將創(chuàng)建一個簡單的電影應(yīng)用凛捏,這個應(yīng)用將抓取目前正在上映的最新的25部電影,并將它們展示在一個ListView中芹缔。
Hello World
react-native init
會復(fù)制Example/SampleProject
中的內(nèi)容到你命名的項目中坯癣,在本文中項目名稱為AwsomeProject
。這是一個簡單的hello world應(yīng)用最欠。你可以通過編輯index.os.js
來改變這個應(yīng)用示罗,然后使用cmd+R
在模擬器中查看變化。
偽造數(shù)據(jù)
在我們開始編寫代碼從Rotten Tomatoes網(wǎng)站抓取數(shù)據(jù)之前芝硬,我們先來偽造一些數(shù)據(jù)以便我們可以馬上體驗一下React Native蚜点。在Facebook我們一般會在JS文件的頂部聲明常量,并在后面使用拌阴,但是隨便你加在哪里都好绍绘。在index.ios.js
中添加以下代碼:
var MOCKED_MOVIES_DATA = [
{title: 'Title', year: '2015', posters: {thumbnail: 'http://i.imgur.com/UePbdph.jpg'}},
];
渲染一部電影
我們會渲染電影標(biāo)題,年份以及電影海報略縮圖迟赃。由于略縮圖在React Native中是一個Image組件陪拘,我們需要將Imagei到React的依賴項中。
var {
AppRegistry,
Image,
StyleSheet,
Text,
View,
} = React;
現(xiàn)在我們修改render函數(shù)以便我們可以將上面渲染上面的數(shù)據(jù)而不僅僅是渲染一個hello world:
render: function() {
var movie = MOCKED_MOVIES_DATA[0];
return (
<View style={styles.container}>
<Text>{movie.title}</Text>
<Text>{movie.year}</Text>
<Image source={{uri: movie.posters.thumbnail}} />
</View>
);
}
按下cmd+R
你應(yīng)該在”2015”上面看到”Title”纤壁。注意此時Image什么都不會渲染左刽。這是因為我們還沒有指定想要的寬度和高度。這需要通過styles屬性來設(shè)置摄乒。在我們修改styles的同時我們還需要把那些不再會使用的樣式刪除:
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
thumbnail: {
width: 53,
height: 81,
},
});
最后我們需要將樣式運用在Image組件上悠反。
<Image
source={{uri: movie.posters.thumbnail}}
style={styles.thumbnail}
/>
按下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 (
<View style={styles.container}>
<Image
source={{uri: movie.posters.thumbnail}}
style={styles.thumbnail}
/>
<View style={styles.rightContainer}>
<Text style={styles.title}>{movie.title}</Text>
<Text style={styles.year}>{movie.year}</Text>
</View>
</View>
);
現(xiàn)在并沒有多少變化,我們在文字外層添加了一個包裹容器并將其放在了圖片后面(因為文字要在圖片的右邊)〉┪現(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ù)據(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/movies/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.movies === null
來確定電影數(shù)據(jù)有沒有被城管加載。當(dāng)電影數(shù)據(jù)返回時列敲,我們可以通過this.setState({movies: moviesData})
來設(shè)置數(shù)據(jù)阱佛。將下面的代碼添加到render函數(shù)之前:
getInitialState: function() {
return {
movies: null,
};
},
我們想要在組件完成加載后發(fā)送請求, componentDidMount
是React組件中的一個函數(shù)酿炸,它只會在組件加載完成之后被調(diào)用一次瘫絮。
componentDidMount: function() {
this.fetchData();
},
現(xiàn)在添加組件中會用到的fetchData
函數(shù)涨冀。這個方法將負(fù)責(zé)處理數(shù)據(jù)抓取填硕。你需要做的僅僅是在promise完成解析之后調(diào)用this.setState({movies: data})
,因為setState
會觸發(fā)重新渲染鹿鳖,而此時render函數(shù)會注意到this.state.movies
不再是null扁眯。注意我們會在promise鏈的最后調(diào)用done()
,一定要確保調(diào)用done()
,否則錯誤信息可能會被忽略。
fetchData: function() {
fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => {
this.setState({
movies: responseData.movies,
});
})
.done();
},
現(xiàn)在修改render函數(shù)來渲染一個loading視圖翅帜,如果電影數(shù)據(jù)還沒有返回的話姻檀,否則將渲染第一部電影:
render: function() {
if (!this.state.movies) {
return this.renderLoadingView();
}
var movie = this.state.movies[0];
return this.renderMovie(movie);
},
renderLoadingView: function() {
return (
<View style={styles.container}>
<Text>
Loading movies...
</Text>
</View>
);
},
renderMovie: function(movie) {
return (
<View style={styles.container}>
<Image
source={{uri: movie.posters.thumbnail}}
style={styles.thumbnail}
/>
<View style={styles.rightContainer}>
<Text style={styles.title}>{movie.title}</Text>
<Text style={styles.year}>{movie.year}</Text>
</View>
</View>
);
},
現(xiàn)在按下cmd+R
,你應(yīng)該已經(jīng)看到了”Loading movies…”涝滴,直到電影數(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 (
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderMovie}
style={styles.listView}
/>
);
}
DataSource
是一個ListView
的接口,作用是決定那些行會被改變杭朱。
注意在這里使用dataSource
而不是this.state
阅仔。下一步我們需要在getInitialState
的返回對象上添加一個空的dataSource
,我們不能再使用this.state.movies
防止數(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.movies),
loaded: true,
});
})
.done();
},
最后八酒,我們在styles
中為ListView
組件添加樣式:
listView: {
paddingTop: 20,
backgroundColor: '#F5FCFF',
},
下面是最終的效果圖:
接下來我們還可以通過添加導(dǎo)航,搜索刃唐,無線滾動加載等等來完成一個完整的應(yīng)用丘跌。你可以查看[電影示例](Movies 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/movies/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.movies),
loaded: true,
});
})
.done();
},
render: function() {
if (!this.state.loaded) {
return this.renderLoadingView();
}
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderMovie}
style={styles.listView}
/>
);
},
renderLoadingView: function() {
return (
<View style={styles.container}>
<Text>
Loading movies...
</Text>
</View>
);
},
renderMovie: function(movie) {
return (
<View style={styles.container}>
<Image
source={{uri: movie.posters.thumbnail}}
style={styles.thumbnail}
/>
<View style={styles.rightContainer}>
<Text style={styles.title}>{movie.title}</Text>
<Text style={styles.year}>{movie.year}</Text>
</View>
</View>
);
},
});
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);