這篇教程旨在讓你使用 React Native 快速的開(kāi)發(fā) iOS 和 Android 應(yīng)用。如果你會(huì)想什么是 React Native 并且為什么 Facebook 構(gòu)建了它,這篇 文章 解釋了為什么。
我們期望你有使用 React 來(lái)寫(xiě)應(yīng)用的經(jīng)驗(yàn)。如果沒(méi)有,你可以在 React website 學(xué)到。
React Native 需要一些在 開(kāi)始 React Native 中闡明的基本的安裝。
在完成了這些依賴項(xiàng)的安裝之后,這里有兩條可以為一個(gè) React Native 項(xiàng)目完全準(zhǔn)備好的命令。
npm install -g react-native-cli
react-native-cli 是完成剩余安裝的命令行工具。它是通過(guò) npm 安裝的。這將會(huì)在你的終端里面安裝 react-native
這個(gè)命令行,你只需要做一次即可。
react-native init AwesomeProject
這一條命令獲取 React Native 的源代碼和依賴包,然后在 AwesomeProject/iOS/AwesomeProject.xcodeproj
創(chuàng)建一個(gè)新的 Xcode 項(xiàng)目,并且在 AwesomeProject/android/app
下面創(chuàng)建一個(gè) gradle 項(xiàng)目。
在 iOS 端,現(xiàn)在你可以在 Xcode 里面打開(kāi)這個(gè)新項(xiàng)目 (AwesomeProject/AwesomeProject.xcodeproj
),然后使用 ?+R
來(lái)簡(jiǎn)單的構(gòu)建和運(yùn)行這個(gè)項(xiàng)目。這樣做也會(huì)開(kāi)啟允許代碼實(shí)時(shí)渲染的 Node 服務(wù)器。有了它你可以通過(guò)在模擬器里面按住?+R
來(lái)看你的更改,而不用在 Xcode 里面重新編譯。
在 Android 端,在 AwesomeProject
里面運(yùn)行 react-native run-android
來(lái)在你的模擬器設(shè)備上面安裝生成的應(yīng)用,并且開(kāi)啟允許代碼實(shí)時(shí)渲染的 Node 服務(wù)器。為了看到你的更改你必須打開(kāi)震動(dòng)菜單(搖動(dòng)你的設(shè)備或者按住設(shè)備上面的菜單按鈕,在模擬器上面按住 F2 或者 Page Up,在 Genymotion 上面按住 ?+M),然后點(diǎn)擊 Reload JS
。
在這篇教程里面我們會(huì)開(kāi)發(fā)一個(gè)簡(jiǎn)單版本的電影應(yīng)用,該應(yīng)用可以獲取電影院里面的 25 部電影,并且將它們顯示在 ListView 里面。
react-native init
將會(huì)生成和你的工程名字一樣的應(yīng)用,在這個(gè)例子中就是 AwesomeProject。這是一個(gè)簡(jiǎn)單的 hello world 應(yīng)用。在 iOS 上面你可以編輯 index.ios.js
來(lái)給這個(gè)應(yīng)用做一些改變,然后在模擬器里面按住 ?+R 來(lái)看發(fā)生的改變。在 Android 上面可以編輯 index.android.js
來(lái)給你的應(yīng)用做一些改變,并且按住震動(dòng)菜單上面的 Reload JS
來(lái)看發(fā)生的改變。
在我們書(shū)寫(xiě)代碼來(lái)獲取真正的 Rotten Tomatoes 數(shù)據(jù)之前,我們可以偽造一些數(shù)據(jù)開(kāi)始使用 React Native。在 Facebook 我們經(jīng)常會(huì)在 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'}}, ];
我們將要給這部電影渲染標(biāo)題,年份,縮略圖。因?yàn)榭s略圖在 React Native 里面是一個(gè)圖片組件,在下面的 React requires 里面增加 Image。
var { AppRegistry, Image, StyleSheet, Text, View, } = React;
現(xiàn)在我們改變這個(gè)渲染函數(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
然后你就會(huì)看到在 "2015" 上面的 "Title" 。注意 Image 并不會(huì)渲染任何東西。這是因?yàn)槲覀儧](méi)有給我們想要渲染的圖片增加寬度和高度。這將會(huì)由樣式來(lái)完成。讓我們?cè)诟淖儤邮降臅r(shí)候我們可以清除一些我們不再使用的樣式。
var styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, thumbnail: { width: 53, height: 81, }, });
最后我們需要將這個(gè)樣式應(yīng)用到這個(gè)圖片組件上面:
<Image source={{uri: movie.posters.thumbnail}} style={styles.thumbnail} />
按住 ?+R
/ Reload JS
現(xiàn)在這個(gè)圖片就會(huì)渲染了。
太棒了,我們已經(jīng)渲染了我們的數(shù)據(jù)?,F(xiàn)在讓我們讓它看起來(lái)更美觀一點(diǎn)。我將會(huì)將文字放在圖片的右邊,并且讓標(biāo)題更大,然后在區(qū)域里面居中。
+---------------------------------+|+-------++----------------------+||| || Title || || Image || || || || Year || |+-------++----------------------+|+---------------------------------+
我們需要增加另外一個(gè)容器來(lái)垂直的展開(kāi)在水平方向上面展開(kāi)的組件。
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> );
沒(méi)有改變很多,我們?cè)谖谋就饷嬖黾恿艘粋€(gè)容器,然后將它們移動(dòng)到圖片后面(因?yàn)樗鼈冊(cè)趫D片右邊)?,F(xiàn)在讓我們看看樣式都改變了什么:
container: { flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', },
我們使用 FlexBox 來(lái)布局-可以看看 這篇文章 來(lái)了解更多。
在上面的代碼片段里面,我們簡(jiǎn)單的增加了 flexDirection: 'row'
,這將會(huì)讓在主容器里面的孩子節(jié)點(diǎn)水平的展開(kāi)而不是垂直展開(kāi)。
現(xiàn)在我們給這個(gè) JS 的 style
對(duì)象增加另外一個(gè)樣式:
rightContainer: { flex: 1, },
這意味著這個(gè) rightContainer
在沒(méi)有被圖片占據(jù)的父容器里面占據(jù)了剩余的空間。如果這不起作用的話,給rightContainer
增加一個(gè) backgroundColor
并且嘗試著移除flex: 1
。你將會(huì)看到這將會(huì)導(dǎo)致父容器的大小將會(huì)變?yōu)槟軌蛉菁{孩子視圖的最小大小。
給文本加上樣式就很直接了:
title: { fontSize: 20, marginBottom: 8, textAlign: 'center', }, year: { textAlign: 'center', },
然后按住 ?+R
/ Reload JS
你就會(huì)看到更新后的視圖了。
從 Rotten Tomatoes 的 API獲取數(shù)據(jù)并不和學(xué)習(xí) React Native 有任何關(guān)系,因此繼續(xù)學(xué)習(xí)下去吧。
在這個(gè)文件的頂部增加下面的一些常量(通常在 requires 下面)來(lái)創(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
來(lái)看電影數(shù)據(jù)是否被加載。當(dāng)帶有 this.setState({movies: moviesData})
響應(yīng)返回的時(shí)候我們可以設(shè)置數(shù)據(jù)。就在我們的 React 類里面的渲染函數(shù)上面增加這段代碼:
getInitialState: function() { return { movies: null, }; },
我們想在組件完成加載的時(shí)候關(guān)閉請(qǐng)求。 在組件被加載之后,componentDidMount
是 React 組件里面只會(huì)調(diào)用一次的函數(shù)。
componentDidMount: function() { this.fetchData(); },
現(xiàn)在給我們的主組件增加上面是用到的 fetchData
。這個(gè)方法將會(huì)負(fù)責(zé)處理數(shù)據(jù)的獲取。你需要做的就是在解決預(yù)期的問(wèn)題之后調(diào)用 this.setState({movies: data})
函數(shù)。因?yàn)?React 的工作方式是:setState
會(huì)觸發(fā)一個(gè)從新渲染,之后渲染函數(shù)就會(huì)注意到 this.state.movies
不再為 null
。注意我們?cè)谧詈笳{(diào)用 done()
-請(qǐng)總是確保調(diào)用 done()
否則任何拋出的錯(cuò)誤信息都會(huì)被隱藏。
fetchData: function() { fetch(REQUEST_URL) .then((response) => response.json()) .then((responseData) => { this.setState({ movies: responseData.movies, }); }) .done(); },
現(xiàn)在修改這個(gè)渲染函數(shù)來(lái)渲染一個(gè)加載的視圖,如果我們沒(méi)有任何電影數(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)返回的時(shí)候你會(huì)看到 "Loading movies..." ,之后它就會(huì)渲染從 Rotten Tomatoes 獲取到的第一部電影。
現(xiàn)在讓我們來(lái)修改這個(gè)應(yīng)用來(lái)在一個(gè) ListView
組件里面渲染所有的數(shù)據(jù),而不是只是渲染第一部電影。
為什么一個(gè) ListView
比只渲染所有的元素或者將它們放到一個(gè) ScrollView
要好一些?盡管 React 很快,但是渲染一個(gè)不確定列表的元素可能就會(huì)慢。ListView
渲染視圖,因此你只在屏幕上顯示要顯示的視圖,那些已經(jīng)渲染過(guò)但是不在屏幕上顯示的就會(huì)被從原生視圖層移除。
第一件事就是快:在這個(gè)文件頂部增加 ListView
必須項(xiàng)。
var { AppRegistry, Image, ListView, StyleSheet, Text, View, } = React;
現(xiàn)在修改渲染函數(shù),因此一旦我們獲取到了數(shù)據(jù),它就會(huì)渲染一個(gè)列表的電影而不只是一部電影。
render: function() { if (!this.state.loaded) { return this.renderLoadingView(); } return ( <ListView dataSource={this.state.dataSource} renderRow={this.renderMovie} style={styles.listView} /> ); },
這個(gè) DataSource
是一個(gè)被 ListView
用來(lái)決定在更新的過(guò)程中哪一行被改變了的接口。
你會(huì)注意到我們從 this.state
來(lái)使用 dataSource
。下一步就是給由 getInitialState
返回的對(duì)象增加一個(gè)空的dataSource
。既然我們?cè)?nbsp;dataSource
里面存放數(shù)據(jù),我們不應(yīng)該在此使用 this.state.movies
來(lái)保存數(shù)據(jù)兩次。我們可以使用狀態(tài) (this.state.loaded
) 的布爾屬性來(lái)判斷獲取數(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 對(duì)象增加樣式:
listView: { paddingTop: 20, backgroundColor: '#F5FCFF', },
現(xiàn)在這是最終結(jié)果:
這里仍然有一些工作要做來(lái)讓它稱為一個(gè)功能完全的應(yīng)用,比如:增加導(dǎo)航欄,搜索框,下拉刷新加載等。在 Movies Example 來(lái)看全部的功能。
/** * 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);
更多建議: