從本章開始我們將一起來開發(fā)一個(gè)簡單的微信精選列表應(yīng)用。主要包括兩個(gè)界面,主界面是微信精選列表,點(diǎn)擊每一條信息可以進(jìn)入到詳細(xì)信息展示的界面。通過這個(gè)應(yīng)用我們將學(xué)習(xí)如何從網(wǎng)絡(luò)獲取數(shù)據(jù)、怎樣對UI組件進(jìn)行布局及設(shè)置樣式、以及常用UI組件的使用。
可以在React Native和React的官方網(wǎng)站查看到更多ReactNative及React的相關(guān)內(nèi)容。對應(yīng)的中文社區(qū):React Native與React。
項(xiàng)目完整源碼
項(xiàng)目的完整源碼下載地址。
注意
一、教程以開放IOS應(yīng)用為基礎(chǔ)講解。
二、涉及到的原生代碼采用Objective-C語言完成。
微信精選
單條內(nèi)容
可以將微信精選列表看做一個(gè)tableview,那么每一條內(nèi)容就是一個(gè)cell。我們將從搭建單條內(nèi)容的界面開始講解。先看一下最終完成的樣子:

這個(gè)界面主要包括文本和圖片,采用文本堆疊在圖片之上的布局方式。
開發(fā)React Native應(yīng)用主要需要JavaScript、CSS和JSX三種技術(shù)。其中JavaScript負(fù)責(zé)業(yè)務(wù)邏輯;CSS負(fù)責(zé)UI樣式和布局;JSX將UI組件封裝為標(biāo)簽語言,使我們可以通過JSX標(biāo)簽構(gòu)建UI。React Native的這種開發(fā)模式借鑒了傳統(tǒng)的web開發(fā)。這樣做主要的優(yōu)勢是可以將內(nèi)容和表現(xiàn)分離,JSX負(fù)責(zé)內(nèi)容,CSS負(fù)責(zé)表現(xiàn)。代碼邏輯更加清晰。
JSX
相信大部分同學(xué)對于JavaScript和CSS肯定不陌生。如果沒有使用React的經(jīng)驗(yàn)可能不了解JSX是什么。那么就來說一說JSX,先來看一段代碼:
<View>
<Text>
JSX,JavaScript語法擴(kuò)展
</Text>
</View>
上面就是一段JSX代碼。
每一個(gè)UI閉合的標(biāo)簽都稱之為組件,<View/>組件可以認(rèn)為代表UIView,<Text/>組件可以認(rèn)為代表UILabel。ReactNative就是通過這些JSX組件與原生UI一一對應(yīng)起來的。
JSX,本質(zhì)是JavaScript的語法擴(kuò)展。React在運(yùn)行期將JSX轉(zhuǎn)換為JavaScript代碼。
JSX以<起始,以/>結(jié)束的閉合標(biāo)簽。JSX級能夠解析HTML標(biāo)簽,也能夠解析React組件。規(guī)定HTML標(biāo)簽必須以小寫字母開頭表示,React組件以大寫字母開頭。
JSX,在構(gòu)建ReactNative應(yīng)用并不是必須的,也可以直接通過JavaScript創(chuàng)建UI組件。但是使用JSX能夠讓我們的代碼更加直觀清晰,并且JSX支持與JavaScript混編,這也賦予其非常大的靈活性。
Flexbox彈性盒
搭建UI界面必須解決的一個(gè)問題就是屏幕適配的問題。在IOS開發(fā)中我們使用autolayout進(jìn)行布局,ReactNative也為我們提供了它解決布局問題的技術(shù)——Flexbox彈性盒。Flexbox本身是CSS3的技術(shù)標(biāo)準(zhǔn),ReactNative因?yàn)槭褂肅SS進(jìn)行布局和樣式設(shè)置,所以也就順理成章的使用Flexbox彈性盒技術(shù)。
可以通過下面的代碼在組件中聲明樣式:
<View style = {styles.container}>
<Text style = {styles.content}>
JSX,JavaScript語法擴(kuò)展
</Text>
</View>
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
});
ReactNative并沒有支持全部的CSS屬性,ReactNative支持的CSS屬性可以看這里。關(guān)于Flexbox彈性盒詳細(xì)的屬性可以查看這里。
關(guān)于Flexbox的教程推薦這兩篇文章:Flex 布局教程:語法篇、Flex 布局教程:實(shí)例篇。
編寫單條內(nèi)容界面
了解了JSX與Flexbox之后,我們就可以正式開始編寫單條內(nèi)容的界面。下面是完整的代碼:
'use strict';
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Image,
} from 'react-native'
class ReactNativeLearn extends Component {
render() {
return (
<View style={styles.container}>
<Image
style={{
width: 300,
height: 200,
}}
resizeMode={"contain"}
source={{uri:'http://facebook.github.io/react/img/logo_og.png'}}
/>
<Text
style={{
color: 'black',
fontSize: 16,
fontWeight: 'normal',
fontFamily: 'Helvetica Neue',
}}>
新聞內(nèi)容
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
});
AppRegistry.registerComponent('ReactNativeLearn', () => ReactNativeLearn);
啟動(dòng)Xcode,運(yùn)行工程:

可以看到我們已經(jīng)實(shí)現(xiàn)簡單的上下布局的圖片和文本。
現(xiàn)在我們來分析一下上面的代碼。首先是'use strict',這是JavaScript的嚴(yán)格模式,在嚴(yán)格模式下一些可能會(huì)帶來安全隱患的JavaScript語言特性將被禁用(ES6標(biāo)準(zhǔn)Module默認(rèn)采用嚴(yán)格模式)。然后是import React, { Component } from 'react'這行代碼的意思是將React庫的Component組件導(dǎo)入到當(dāng)前作用域中。這是采用ES6標(biāo)準(zhǔn)的寫法。之后的代碼就是我們創(chuàng)建界面的主要代碼。我們可以看到render()函數(shù),render是渲染的意思,我們在render函數(shù)中創(chuàng)建我們的UI組件渲染到屏幕。
關(guān)于組件的詳細(xì)信息可以在ReactNative官網(wǎng)找到。AppRegistry.registerComponent('ReactNativeLearn', () => ReactNativeLearn),表示將ReactNativeLearn組件作為整個(gè)ReactNative應(yīng)用的入口。ReactNativeLearn的名稱與AppDelegate中的moduleName對應(yīng):
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"ReactNativeLearn"
initialProperties:nil
launchOptions:launchOptions];
現(xiàn)在我們可以通過修改組件的style屬性和組件的包裹方式,實(shí)現(xiàn)我們想要的最終效果。代碼如下:
class ReactNativeLearn extends Component {
render() {
return (
<View style={styles.container}>
<Image
style={{
width: Dimensions.get('window').width - 40,
height: 300,
marginLeft: 20,
marginRight: 20,
marginTop: 10,
marginBottom: 10,
borderColor: "rgb(0,0,0)",
borderWidth: 2,
borderRadius: 8,
justifyContent: 'flex-end',
}}
resizeMode={"stretch"}
source={{uri:'http://facebook.github.io/react/img/logo_og.png'}}
>
<View
style={{
flex: 0,
justifyContent: 'flex-start',
alignItems: 'stretch',
backgroundColor: "rgba(0,0,0,0.4)",
}}>
<Text
style={{
color: "rgb(255,255,255)",
fontSize: 20,
fontWeight: 'normal',
fontFamily: 'Helvetica Neue',
marginLeft: 10,
marginRight: 10,
marginTop: 10,
marginBottom: 10,
}}
numberOfLines={3}
>
A React component for displaying different types of images, including network images, static resources, temporary local images, and images from local disk, such as the camera roll.
</Text>
</View>
</Image>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#F5FCFF',
},
});
最終效果:

我們不應(yīng)該將全部代碼都寫在index.ios.js文件,而是應(yīng)該分成不同的模塊,方便管理。我們新建一個(gè)名為newsCell的js文件,將代碼復(fù)制到該文件當(dāng)中。最后將newsCell聲明為一個(gè)可以被其他模塊引用的組件:
export default NewsCell;
微信精選列表
完成單條內(nèi)容的UI搭建,現(xiàn)在我們就來實(shí)現(xiàn)列表的界面。在React Native應(yīng)用中列表通常使用ListView組件。ListView的功能類似于UITableView。先實(shí)現(xiàn)最基本的ListView,代碼如下:
class ReactNativeLearn extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
})
};
this.renderRow = this.renderRow.bind(this);
}
componentDidMount() {
this.fetchNewsData();
}
// 設(shè)置數(shù)據(jù)
fetchNewsData() {
this.setState({
dataSource: this.state.dataSource.cloneWithRows([{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'}])
});
}
// 渲染cell
renderRow(rowData) {
return (
<View
style={{
height: 80,
justifyContent: 'flex-start',
alignItems: 'stretch',
backgroundColor: "rgba(74,144,226,1)",
}}>
<Text
style={{
color: 'black',
fontSize: 28,
fontWeight: 'normal',
fontFamily: 'Helvetica Neue',
}}>
{rowData.content}
</Text>
</View>
);
}
// 渲染cell的分割
renderSeparator(
sectionID,
rowID
) {
return (
<View key = {'cell_'+ sectionID + '_' + rowID} style = {styles.rowSeparator}/>
);
}
render() {
return (
<View style={styles.container}>
<ListView
style = {styles.listContainer}
dataSource = {this.state.dataSource}
renderRow = {this.renderRow}
renderSeparator = {this.renderSeparator}
enableEmptySections={true}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#F5FCFF',
},
listContainer: {
flex:1,
marginTop: 64,
backgroundColor: 'rgb(237, 240, 235)',
},
rowSeparator: {
backgroundColor: 'rgb(223, 218, 223)',
height: 1,
},
});
最終效果:

現(xiàn)在我們已經(jīng)完成一個(gè)最簡單的ListView??梢钥吹轿覀?yōu)長istView組件設(shè)置了三個(gè)屬性:dataSource,renderRow和renderSeparator。dataSource是ListView的數(shù)據(jù)源類似于UITableView的數(shù)據(jù)源方法,這里我們手動(dòng)設(shè)置了一個(gè)數(shù)組;renderRow接收一個(gè)渲染cell的函數(shù)回調(diào),類似于UITableView的cellForRowAtIndexPath??梢栽谶@個(gè)函數(shù)里我們可以創(chuàng)建cell的UI界面,即我們之前創(chuàng)建的單條內(nèi)容界面。還有一個(gè)renderSeparator,可以用于創(chuàng)建分割cell的UI。
目前為止ListView的每一個(gè)cell的數(shù)據(jù)都是我們手動(dòng)設(shè)置的。實(shí)際開發(fā)中,這些數(shù)據(jù)往往來自于服務(wù)器。下面我們來實(shí)現(xiàn)網(wǎng)絡(luò)請求,代碼如下:
fetchNewsData() {
fetch(newsULR,{
headers: {
"apikey": "f589f2834aeab120eef2e750e4fb1dfb"
}
}).then((response) => response.json())
.catch((error) => {
console.error('error request!');
})
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData.newslist)
});
})
.done();
}
React Native使用Fetch進(jìn)行網(wǎng)絡(luò)請求。Fetch是新的異步網(wǎng)絡(luò)請求標(biāo)準(zhǔn),相較于AJAX提供更強(qiáng)大高效的網(wǎng)絡(luò)請求API。關(guān)于ReactNative的網(wǎng)絡(luò)內(nèi)容可以看這里。
我們已經(jīng)實(shí)現(xiàn)ListView的基本功能,現(xiàn)在需要將之前單條新聞的界面newsCell作為組件,添加到ListView中,實(shí)現(xiàn)完整的列表。引入newsCell的方式與引入ReactNative原生組件的方式相同,都是通過import進(jìn)行導(dǎo)入。
import NewsCell from './News/newsCell';
我們可以通過下面的方式向NewsCell傳遞數(shù)據(jù)或者回調(diào)函數(shù):
renderRow(rowData) {
return (
<NewsCell newsData={rowData}/>
);
}
然后在NewsCell組件可以通過如下的方式獲得數(shù)據(jù):
{this.props.newsData.title}
到此我們完成完整的微信精選列表。最終列表界面:

像newsCell一樣,我們把列表也封裝為一個(gè)單獨(dú)的組件,命名為newsMain,然后直接在index.ios.js文件導(dǎo)入即可。