這篇教程旨在讓你使用 React Native 快速的開發(fā) iOS 和 Android 應(yīng)用。如果你會想什么是 React Native 并且為什么 Facebook 構(gòu)建了它,這篇 文章 解釋了為什么。
我們期望你有使用 React 來寫應(yīng)用的經(jīng)驗。如果沒有,你可以在 React website 學(xué)到。
React Native 需要一些在 開始 React Native 中闡明的基本的安裝。
在完成了這些依賴項的安裝之后,這里有兩條可以為一個 React Native 項目完全準備好的命令。
npm install -g react-native-cli
react-native-cli 是完成剩余安裝的命令行工具。它是通過 npm 安裝的。這將會在你的終端里面安裝 react-native 這個命令行,你只需要做一次即可。
react-native init AwesomeProject
這一條命令獲取 React Native 的源代碼和依賴包,然后在 AwesomeProject/iOS/AwesomeProject.xcodeproj 創(chuàng)建一個新的 Xcode 項目,并且在 AwesomeProject/android/app 下面創(chuàng)建一個 gradle 項目。
在 iOS 端,現(xiàn)在你可以在 Xcode 里面打開這個新項目 (AwesomeProject/AwesomeProject.xcodeproj),然后使用 ?+R 來簡單的構(gòu)建和運行這個項目。這樣做也會開啟允許代碼實時渲染的 Node 服務(wù)器。有了它你可以通過在模擬器里面按住 ?+R 來看你的更改,而不用在 Xcode 里面重新編譯。
在 Android 端,在 AwesomeProject 里面運行 react-native run-android 來在你的模擬器設(shè)備上面安裝生成的應(yīng)用,并且開啟允許代碼實時渲染的 Node 服務(wù)器。為了看到你的更改你必須打開震動菜單(搖動你的設(shè)備或者按住設(shè)備上面的菜單按鈕,在模擬器上面按住 F2 或者 Page Up,在 Genymotion 上面按住 ?+M),然后點擊 Reload JS。
在這篇教程里面我們會開發(fā)一個簡單版本的電影應(yīng)用,該應(yīng)用可以獲取電影院里面的 25 部電影,并且將它們顯示在 ListView 里面。
react-native init 將會生成和你的工程名字一樣的應(yīng)用,在這個例子中就是 AwesomeProject。這是一個簡單的 hello world 應(yīng)用。在 iOS 上面你可以編輯 index.ios.js 來給這個應(yīng)用做一些改變,然后在模擬器里面按住 ?+R 來看發(fā)生的改變。在 Android 上面可以編輯 index.android.js來給你的應(yīng)用做一些改變,并且按住震動菜單上面的 Reload JS 來看發(fā)生的改變。
在我們書寫代碼來獲取真正的 Rotten Tomatoes 數(shù)據(jù)之前,我們可以偽造一些數(shù)據(jù)開始使用 React Native。在 Facebook 我們經(jīng)常會在 JS 文件的頭部申明常量,就在 requires 下面,但是你想增加什么數(shù)據(jù)就增加什么數(shù)據(jù)。在 index.ios.js 或者 index.android.js 里面:
var MOCKED_MOVIES_DATA = [
{title: 'Title', year: '2015', posters: {thumbnail: 'http://i.imgur.com/UePbdph.jpg'}},
];
我們將要給這部電影渲染標題,年份,縮略圖。因為縮略圖在 React Native 里面是一個圖片組件,在下面的 React requires 里面增加 Image。
var {
AppRegistry,
Image,
StyleSheet,
Text,
View,
} = React;
現(xiàn)在我們改變這個渲染函數(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>
);
}
按住 ?+R / Reload JS 然后你就會看到在 "2015" 上面的 "Title" 。注意 Image 并不會渲染任何東西。這是因為我們沒有給我們想要渲染的圖片增加寬度和高度。這將會由樣式來完成。讓我們在改變樣式的時候我們可以清除一些我們不再使用的樣式。
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
thumbnail: {
width: 53,
height: 81,
},
});
最后我們需要將這個樣式應(yīng)用到這個圖片組件上面:
<Image
source={{uri: movie.posters.thumbnail}}
style={styles.thumbnail}
/>
按住 ?+R / Reload JS 現(xiàn)在這個圖片就會渲染了。


太棒了,我們已經(jīng)渲染了我們的數(shù)據(jù)?,F(xiàn)在讓我們讓它看起來更美觀一點。我將會將文字放在圖片的右邊,并且讓標題更大,然后在區(qū)域里面居中。
+---------------------------------+
|+-------++----------------------+|
|| || Title ||
|| Image || ||
|| || Year ||
|+-------++----------------------+|
+---------------------------------+
我們需要增加另外一個容器來垂直的展開在水平方向上面展開的組件。
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>
);
沒有改變很多,我們在文本外面增加了一個容器,然后將它們移動到圖片后面(因為它們在圖片右邊)?,F(xiàn)在讓我們看看樣式都改變了什么:
container: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
我們使用 FlexBox 來布局-可以看看 這篇文章 來了解更多。
在上面的代碼片段里面,我們簡單的增加了 flexDirection: 'row' ,這將會讓在主容器里面的孩子節(jié)點水平的展開而不是垂直展開。
現(xiàn)在我們給這個 JS 的 style 對象增加另外一個樣式:
rightContainer: {
flex: 1,
},
這意味著這個 rightContainer 在沒有被圖片占據(jù)的父容器里面占據(jù)了剩余的空間。如果這不起作用的話,給 rightContainer 增加一個 backgroundColor 并且嘗試著移除flex: 1。你將會看到這將會導(dǎo)致父容器的大小將會變?yōu)槟軌蛉菁{孩子視圖的最小大小。
給文本加上樣式就很直接了:
title: {
fontSize: 20,
marginBottom: 8,
textAlign: 'center',
},
year: {
textAlign: 'center',
},
然后按住 ?+R / Reload JS 你就會看到更新后的視圖了。


從 Rotten Tomatoes 的 API獲取數(shù)據(jù)并不和學(xué)習(xí) React Native 有任何關(guān)系,因此繼續(xù)學(xué)習(xí)下去吧。
在這個文件的頂部增加下面的一些常量(通常在 requires 下面)來創(chuàng)建獲取數(shù)據(jù)的 REQUEST_URL。
/**
* For quota reasons we replaced the Rotten Tomatoes' API with a sample data of
* their very own API that lives in React Native's Github repo.
*/
var REQUEST_URL = 'https://raw.githubusercontent.com/facebook/react-native/master/docs/MoviesExample.json';
給我們的應(yīng)用增加一些初始化的狀態(tài),因此我們可以檢查 this.state.movies === null 來看電影數(shù)據(jù)是否被加載。當帶有 this.setState({movies: moviesData}) 響應(yīng)返回的時候我們可以設(shè)置數(shù)據(jù)。就在我們的 React 類里面的渲染函數(shù)上面增加這段代碼:
getInitialState: function() {
return {
movies: null,
};
},
我們想在組件完成加載的時候關(guān)閉請求。 在組件被加載之后,componentDidMount 是 React 組件里面只會調(diào)用一次的函數(shù)。
componentDidMount: function() {
this.fetchData();
},
現(xiàn)在給我們的主組件增加上面是用到的 fetchData。這個方法將會負責處理數(shù)據(jù)的獲取。你需要做的就是在解決預(yù)期的問題之后調(diào)用 this.setState({movies: data}) 函數(shù)。因為 React 的工作方式是:setState 會觸發(fā)一個從新渲染,之后渲染函數(shù)就會注意到 this.state.movies 不再為 null。注意我們在最后調(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)在修改這個渲染函數(shù)來渲染一個加載的視圖,如果我們沒有任何電影數(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)在按住 ?+R / Reload JS 然后直到響應(yīng)返回的時候你會看到 "Loading movies..." ,之后它就會渲染從 Rotten Tomatoes 獲取到的第一部電影。


現(xiàn)在讓我們來修改這個應(yīng)用來在一個 ListView 組件里面渲染所有的數(shù)據(jù),而不是只是渲染第一部電影。
為什么一個 ListView 比只渲染所有的元素或者將它們放到一個 ScrollView 要好一些?盡管 React 很快,但是渲染一個不確定列表的元素可能就會慢。ListView 渲染視圖,因此你只在屏幕上顯示要顯示的視圖,那些已經(jīng)渲染過但是不在屏幕上顯示的就會被從原生視圖層移除。
第一件事就是快:在這個文件頂部增加 ListView 必須項。
var {
AppRegistry,
Image,
ListView,
StyleSheet,
Text,
View,
} = React;
現(xiàn)在修改渲染函數(shù),因此一旦我們獲取到了數(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 用來決定在更新的過程中哪一行被改變了的接口。
你會注意到我們從 this.state 來使用 dataSource。下一步就是給由 getInitialState 返回的對象增加一個空的dataSource。既然我們在 dataSource 里面存放數(shù)據(jù),我們不應(yīng)該在此使用 this.state.movies 來保存數(shù)據(jù)兩次。我們可以使用狀態(tài) (this.state.loaded) 的布爾屬性來判斷獲取數(shù)據(jù)是否完成。
getInitialState: function() {
return {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
}),
loaded: false,
};
},
這里是更具狀態(tài)更新的修改之后的 fetchData:
fetchData: function() {
fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData.movies),
loaded: true,
});
})
.done();
},
最后我們給 ListView 組件的 styles JS 對象增加樣式:
listView: {
paddingTop: 20,
backgroundColor: '#F5FCFF',
},
現(xiàn)在這是最終結(jié)果:


這里仍然有一些工作要做來讓它稱為一個功能完全的應(yīng)用,比如:增加導(dǎo)航欄,搜索框,下拉刷新加載等。在 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);