又是一篇關(guān)于React Native的實(shí)例, BBC新聞客戶端. 通過訪問BBC的公開網(wǎng)絡(luò)接口, 獲取新聞內(nèi)容, 也可以根據(jù)類型顯示. 在編寫代碼中, 學(xué)習(xí)RN的知識(shí), 源碼是使用ES6的規(guī)范編寫, 符合Facebook的RN代碼最新規(guī)范.

主要技術(shù)
- 訪問網(wǎng)絡(luò)請(qǐng)求, 過濾內(nèi)容, 獲取數(shù)據(jù).
- 顯示多媒體頁(yè)面, 圖片, 視頻, 鏈接等.
本文源碼的GitHub下載地址
關(guān)于React Native項(xiàng)目的啟動(dòng), 參考1, 參考2.
配置項(xiàng)目
初始化項(xiàng)目WclBBCNews, 修改package.json, 添加依賴庫(kù).
Html解析庫(kù): htmlparser, 時(shí)間處理庫(kù): moment, 線性梯度庫(kù): react-native-linear-gradient, 視頻庫(kù): react-native-video.
"dependencies": {
"react": "^0.14.8",
"react-native": "^0.24.1",
"htmlparser": "^1.7.7",
"moment": "^2.11.1",
"react-native-linear-gradient": "^1.4.0",
"react-native-video": "^0.6.1"
}
目前, React Native禁止使用
-初始化項(xiàng)目名稱, 最好使用駝峰式.
初始化主模塊index.ios.js, 使用NavigatorIOS導(dǎo)航頁(yè)面, 首頁(yè)組件Feed模塊.
render() {
return (
<NavigatorIOS
style={{flex:1}}
translucent={false}
barTintColor={'#BB1919'}
titleTextColor={'white'}
tintColor={'white'}
initialRoute={{
component: Feed,
title: "Feed",
passProps: {}
}}/>
);
}
渲染使用動(dòng)態(tài)加載組件, StatusBar使用淺色樣式.
_renderScene(route, navigator) {
var Component = route.component;
StatusBar.setBarStyle('light-content');
return (
<Component
{...route.props}
changeNavBarHeight={this.changeNavBarHeight}
navigator={navigator}
route={route}/>
);
}
StatusBar樣式只有兩種, 默認(rèn)
default, 字是黑色; 可選light-content, 字是白色.
新聞列表
Feed頁(yè)面, 主要以列表形式, 即ListView標(biāo)簽, 顯示新聞. 未加載完成時(shí), 調(diào)用頁(yè)面加載提示符ActivityIndicatorIOS, 顯示動(dòng)畫.
render() {
// 未加載完成時(shí), 調(diào)用加載頁(yè)面
if (!this.state.loaded) {
return this._renderLoading();
}
// ...
}
_renderLoading() {
return (
<View style={{flexDirection: 'row', justifyContent: 'center', flex: 1}}>
<ActivityIndicatorIOS
animating={this.state.isAnimating}
style={{height: 80}}
size="large"/>
</View>
);
}
加載完成后, 調(diào)用ListView顯示頁(yè)面, renderRow渲染每一行, refreshControl加載頁(yè)面的過場(chǎng).
return (
<ListView
testID={"Feed Screen"}
dataSource={this.state.dataSource}
renderRow={this._renderStories.bind(this)}
style={{backgroundColor: '#eee'}}
contentInset={{top:0, left:0, bottom: 64, right: 0}}
scrollEventThrottle={200}
{...this.props}
refreshControl={
<RefreshControl
refreshing={this.state.isRefreshing}
onRefresh={this._fetchData.bind(this)}
tintColor='#BB1919'
title="Loading..."
progressBackgroundColor="#FFFF00"
/>}
/>
);
每一行使用Story模塊渲染.
_renderStories(story) {
return (
<Story story={story} navigator={this.props.navigator}/>
);
}
啟動(dòng)頁(yè)面的時(shí)候, 使用fetch方法加載數(shù)據(jù).
componentDidMount() {
this._fetchData();
}
通過訪問BBC的網(wǎng)絡(luò)請(qǐng)求, 異步獲取數(shù)據(jù). 使用_filterNews過濾需要的數(shù)據(jù), 把數(shù)據(jù)設(shè)置入每一行, 修改狀態(tài)setState, 重新渲染頁(yè)面.
_fetchData() {
this.setState({isRefreshing: true});
fetch(`http://trevor-producer-cdn.api.bbci.co.uk/content${this.props.collection || '/cps/news/world'}`)
.then((response) => response.json())
.then((responseData) => this._filterNews(responseData.relations))
.then((newItems) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(newItems),
loaded: true,
isRefreshing: false,
isAnimating: false
})
}).done();
}
列表項(xiàng)提供分類顯示功能, 點(diǎn)擊類別, 可以重新加載所選類型的新聞, 把Feed頁(yè)面再次添加至導(dǎo)航navigator, 即頁(yè)面棧.
_pressedCollection(collection) {
this.props.navigator.push({
component: Feed,
title: collection.content.name,
passProps: {
collection: collection.content.id,
navigator: this.props.navigator
}
});
}
點(diǎn)擊列表項(xiàng), 跳轉(zhuǎn)至詳情頁(yè)面StoryDetail.
_pressedStory(story) {
this.props.navigator.push({
component: StoryDetail,
title: this._truncateTitle(story.content.name),
passProps: {story, navigator: this.props.navigator}
});
}
效果

新聞詳情
主要是解析HTML頁(yè)面, 加載并顯示, 除了文字之外, 會(huì)顯示圖片\視頻\超鏈接等樣式. 渲染使用動(dòng)態(tài)元素, 狀態(tài)state的elements屬性.
render() {
if (this.state.loading) {
return (
<Text>Loading</Text>
);
}
return this.state.elements;
}
頁(yè)面啟動(dòng)時(shí), 加載數(shù)據(jù). 在_fetchStoryData方法中, 進(jìn)行處理, 使用回調(diào)返回?cái)?shù)據(jù). 主要內(nèi)容body與多媒體media通過滾動(dòng)視圖ScrollView的形式顯示出來.
componentDidMount() {
this._fetchStoryData(
// media表示視頻或圖片.
(result, media) => {
const rootElement = result.find(item => {
return item.name === 'body';
});
XMLToReactMap.createReactElementsWithXMLRoot(rootElement, media)
.then(array => {
var scroll = React.createElement(ScrollView, {
contentInset: {top: 0, left: 0, bottom: 64, right: 0},
style: {flex: 1, flexDirection: 'column', backgroundColor: 'white'},
accessibilityLabel: "Story Detail"
}, array);
this.setState({loading: false, elements: scroll});
});
}
);
}
處理數(shù)據(jù), 使用fetch方法, 分離視頻與圖片, 還有頁(yè)面, 通過回調(diào)cb(callback)的處理返回?cái)?shù)據(jù).
_fetchStoryData(cb) {
// 提取數(shù)據(jù), 轉(zhuǎn)換JSON格式, 圖片過濾, 視頻過濾, 組合relations, 解析.
fetch(`http://trevor-producer-cdn.api.bbci.co.uk/content${this.props.story.content.id}`)
.then((response) => response.json())
.then((responseData) => {
const images = responseData.relations.filter(item => {
return item.primaryType === 'bbc.mobile.news.image';
});
const videos = responseData.relations.filter(item => {
return item.primaryType === 'bbc.mobile.news.video';
});
const relations = {images, videos};
this._parseXMLBody(responseData.body, (result) => {
cb(result, relations);
});
}).done();
}
使用Tautologistics解析dom數(shù)據(jù)與body數(shù)據(jù). DOM, 即Document Object Model, 文件對(duì)象模型.
_parseXMLBody(body, cb) {
var handler = new Tautologistics.NodeHtmlParser.DefaultHandler(
function (error, dom) {
cb(dom)
}, {enforceEmptyTags: false, ignoreWhitespace: true});
var parser = new Tautologistics.NodeHtmlParser.Parser(handler);
parser.parseComplete(body);
}
XML解析類XMLToReactMap比較復(fù)雜, 不做過多介紹, 參考源碼.

通過編寫新聞?lì)悜?yīng)用, 學(xué)習(xí)使用網(wǎng)絡(luò)請(qǐng)求和解析HTML格式的文本. 多編碼多思考, 不斷學(xué)習(xí), React Native是非常有意思的開發(fā)語(yǔ)言.
感謝我的朋友Joel Trew的實(shí)例, 本文改動(dòng)一些源碼.
OK, that's all! Enjoy it!