看完全文并且do it你將收獲:
- fetch獲取網(wǎng)絡數(shù)據(jù)的基本用法
- ListView的基本用法
- RN中組件的生命周期(有可能)
類似的實現(xiàn)其實有很多朋友已經(jīng)寫過了,我也看了幾篇類似博客
這篇《react-native中l(wèi)istview獲取豆瓣書本信息并展示出來》確實是一個值得學習的例子,但作者只是一步一步貼代碼,沒有很好的細講
這篇《React Native從網(wǎng)絡拉取數(shù)據(jù)并填充列表》寫的不錯,值得一看,不過listview沒有圖片,效果看起來沒有我的有逼格(傲嬌臉)
來,先看下最終我們要實現(xiàn)一個怎么樣的效果吧:

好,正題開始:
要展示出這樣一個listview的效果,首先需要有數(shù)據(jù)源,我這里私藏了兩個能返回json(帶有圖片地址)的url:
url最后的數(shù)字是返回數(shù)據(jù)的數(shù)量,可以自己修改
比如第一個url將返回30條數(shù)據(jù),第二個url將返回3條
我們先看一看返回的json長啥樣,在瀏覽器中輸入url后:
我使用的是chrome瀏覽器,通過插件“JSON-handle”將json格式化并展示了出來,很友好的效果,其他瀏覽器也有類似插件,可自行搞定

果然是好長一段的json!我們需要的內(nèi)容在json>data中,對比預覽圖得知需要的數(shù)據(jù)段為:name、picSmall(當然用小圖啦)、description三個。
OK,既然有了數(shù)據(jù)源,那么接下來要做的就是通過網(wǎng)絡請求去獲取數(shù)據(jù)
RN中的網(wǎng)絡請求通過Fetch進行,最簡單的fetch寫法是傳入一個url參數(shù):
fetch('http://www.imooc.com/api/teacher?type=4&num=30');
通過這一句就成功發(fā)起了一個網(wǎng)絡請求,當然fetch方法還有第二個可選參數(shù),這個參數(shù)用于傳入一些HTTP請求的參數(shù)如:headers、請求方式(GET/POST)等
援引RN中文網(wǎng)fetch一段示例代碼:
fetch('https://mywebsite.com/endpoint/', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
firstParam: 'yourValue',
secondParam: 'yourOtherValue',
})
})
僅用于獲取簡單的數(shù)據(jù)則完全可以忽略第二個參數(shù)
在用fetch請求網(wǎng)絡成功后,會返回的是一個Promise對象,用于檢索我們需要的數(shù)據(jù),但我們需要的是一個json對象來便于處理數(shù)據(jù),那么怎樣將這個Promise對象轉(zhuǎn)換成json對象呢?fetch有非常方便的鏈式調(diào)用方法來搞定:
fetch('https://mywebsite.com/endpoint/')
.then((response) => response.json());//response就是fetch返回的Promise對象
//.then()中傳入一個回調(diào)函數(shù),該回調(diào)函數(shù)接收fetch返回的對象
//你可以在這個時候?qū)romise對象轉(zhuǎn)換成json對象:response.json()
//轉(zhuǎn)換成json對象后return,給下一步的.then處理
沒錯!.then可以一直接下去!并且每一個.then都接收一個回調(diào)函數(shù),這個回調(diào)函數(shù)都接收上一個.then返回的數(shù)據(jù),這樣做數(shù)據(jù)處理豈不是美滋滋!
ok,既然這樣,那我們在下一個.then中接收到這個由上一個.then返回的json對象,并將他設置給this.state以便于后面的使用:
constructor(props){
super(props);
this.state = {
data: null,
};
}
componentDidMount(){
fetch('http://www.imooc.com/api/teacher?type=4&num=30')
.then((response) => response.json())
.then((jsonData) => { //jsonData就是上一步的response.json()
this.setState({
data: jsonData.data, //data是一個對象數(shù)組
});
}) //你后面還可以繼續(xù)瘋狂的接.then并且傳遞數(shù)據(jù),盡管試試吧!
.catch((error) => { //注意尾部添加異?;卣{(diào)
alert(error);
});
}
//上一個.then的回調(diào)函數(shù)返回的數(shù)據(jù),由下一個.then的回調(diào)函數(shù)接收
為啥把fetch寫在componentDidMount()中呢,因為這是一個RN中組件的生命周期回調(diào)函數(shù),會在組件渲染后回調(diào)
關(guān)于更多組件生命周期戳React Native 中組件的生命周期
那么我們最終得到的this.state.data到底長啥樣的?根據(jù)json結(jié)構(gòu)來分析一下,json.data是一個對象數(shù)組,現(xiàn)在賦給了this.state.data,那么this.state.data應該就是一個對象數(shù)組,我們來檢測一下:
componentDidMount(){
fetch('http://www.imooc.com/api/teacher?type=4&num=30')
.then((response) => response.json())
.then((jsonData) => {
this.setState({
data: jsonData.data,
});
let n = this.state.data[0].name;
let d = this.state.data[0].description;
alert("n:" + n + ",d:" + d);
})
.catch((error) => {
alert(error);
});
}
double r:

很好,這說明data是一個對象數(shù)組,通過數(shù)組取值方法取到了值,結(jié)構(gòu)看json那邊,這樣就很好的可以取到其中的值

關(guān)于更多詳細的fetch介紹可以自行G/B,也墻裂建議認真看看、理解fetch
拿到了數(shù)據(jù),接下來開始使用listview了
首先得導入ListView組件(我很容易忘記導入組件),接下來就是把它寫在布局中
使用<ListView>的時候,有兩個必須參數(shù):dataSource、renderRow
<ListView
dataSource={} //必須寫此屬性并且傳入dataSource對象
renderRow={}> //必須寫此屬性并且傳入一個返回component的回調(diào)函數(shù)
</ListView>
這個很容易理解,listview要展示數(shù)據(jù),必須得有數(shù)據(jù)源(dataSource),之后listview必須知道他每一行長啥樣,他才能去渲染(renderRow)
dataSource
dataSource是ListView下的一個類,新建一個dataSource:
let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
//DataSource()可以接收四種參數(shù)
//rowHasChanged就是渲染每一行的依據(jù),r1是上一行的數(shù)據(jù),r2是下一行的數(shù)據(jù)
//此處定義:r1和r2不同時,需要渲染新的一行
關(guān)于DataSource可接收四種參數(shù):
- getRowData(dataBlob, sectionID, rowID):表明我們將以何種方式從dataBlob(數(shù)據(jù)源)中提取出rowData,sectionID用于指定每一個section的標題名(在renderRow,renderHeader等方法中會默認拆開并作為參數(shù)傳入)
- getSectionHeaderData(dataBlob, sectionID):表明我們將以何種方式從dataBlob(數(shù)據(jù)源)中提取出HeaderData。HeaderData用于給每一個sectionHeader賦值
- rowHasChanged(prevRowData, nextRowData):指定我們更新row的策略,一般來說都是prevRowData和nextRowData不相等時更新row
- sectionHeaderHasChanged(prevSectionData, nextSectionData):指定我們更新sectionHeader的策略,一般如果用到sectionData的時候才指定
引自:React-Native組件用法詳解之ListView
==========本次Demo實現(xiàn)只需要用到rowHasChanged
墻裂建議認真學習更多l(xiāng)istview詳細內(nèi)容
這個時候我們創(chuàng)建出來的ds還并不擁有任何數(shù)據(jù),在傳給listview之前還要進行一個步驟:
let lvData = ds.cloneWithRows(this.state.data);
//可以粗暴理解為:將this.state.data克隆到lvData,只有經(jīng)過這一步的數(shù)據(jù)才能給listview使用
我們希望網(wǎng)絡請求到數(shù)據(jù)時立即進行cloneWithRows這一步,這樣this.state.data在網(wǎng)絡請求完成后就直接被賦值成為可以傳遞給listview的對象:
fetch('http://www.imooc.com/api/teacher?type=4&num=30')
.then((response) => response.json())
.then((jsonData) => {
this.setState({
data: new ListView.DataSource({rowHasChanged: (r1,r2) => r1!==r2 }).cloneWithRows(jsonData.data),
});
})
.catch((error) => {
alert(error);
});
renderRow
<listview>的renderRow屬性接收一個“返回每一行的組件的回調(diào)函數(shù)”,該回調(diào)函數(shù)接收一個分配給該行的數(shù)據(jù),在本例中即為json.data中的子項
OK,既然知道renderRow需要啥,并且知道每一行長啥樣:

我們就可以根據(jù)這個樣式來寫一個需要的回調(diào)函數(shù):
export default class lvtest extends Component {
………………
// 返回listview的一行
renderRow(rowData){//參數(shù)為接收的每一行的數(shù)據(jù),理解:數(shù)組data的子項
return(
<View //最外層包裹的View
style = {styles.lvRow}>
<Image //左側(cè)顯示的圖片
style = { styles.img }
source = { { uri: rowData.picSmall } }/>//source為rowData中的picSmall,
//致于你換rowData.picBig也行,更消耗流量加載慢就是
<View //包裹文本的View
style = { styles.textView }>
<Text //標題
style = { styles.textTitle }
numberOfLines = { 1 }> //只顯示一行
{ rowData.name } //取rowData中的name字段,理解:data[某行].name
//注意:this.state.data不能直接當做數(shù)組使用,他是一個dataSource對象
</Text>
<Text //詳細內(nèi)容文本
style = { styles.textContent }>
{ rowData.description } //取rowData中的description字段,理解:data[某行].description
</Text>
</View>
</View>
)
}
}
//樣式,
const styles = StyleSheet.create({
lvRow: {
flex: 1,
flexDirection: 'row',
padding: 10,
},
textView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 5,
},
textTitle: {
flex: 1,
textAlign: 'center',
color: '#f00',
},
textContent: {
flex: 1,
fontSize: 11,
color: '#000',
textAlign: 'center',
},
img: {
height: 55,
width: 100,
}
});
除了rowData.name、rowData.description、rowData.picSmall,我相信大家都很容易看明白這個布局的實現(xiàn),至此我們所有的代碼如下:(還有個坑呢,繼續(xù)看完?。?/p>
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
View,
Text,
ListView
} from 'react-native';
export default class lvtest extends Component {
constructor(props){
super(props);
this.state = {
data: null,
};
}
render() {
return (
<ListView
dataSource={this.state.data}
renderRow={(rowData) => this.renderRow(rowData)}>
</ListView>
);
}
// 生命周期回調(diào)函數(shù),在其中進行網(wǎng)絡請求
componentDidMount(){
fetch('http://www.imooc.com/api/teacher?type=4&num=30')
.then((response) => response.json())
.then((jsonData) => {
this.setState({
data: new ListView.DataSource({rowHasChanged: (r1,r2) => r1!==r2 }).cloneWithRows(jsonData.data),
});
})
.catch((error) => {
alert(error);
});
}
// 返回listview的一行
renderRow(rowData){
return(
<View
style = {styles.lvRow}>
<Image
style = { styles.img }
source = { { uri: rowData.picSmall } }/>
<View
style = { styles.textView }>
<Text
style = { styles.textTitle }
numberOfLines = { 1 }>
{ rowData.name }
</Text>
<Text
style = { styles.textContent }>
{ rowData.description }
</Text>
</View>
</View>
)
}
}
const styles = StyleSheet.create({
lvRow: {
flex: 1,
flexDirection: 'row',
padding: 10,
},
textView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 5,
},
textTitle: {
flex: 1,
textAlign: 'center',
color: '#f00',
},
textContent: {
flex: 1,
fontSize: 11,
color: '#000',
textAlign: 'center',
},
img: {
height: 55,
width: 100,
}
});
AppRegistry.registerComponent('lvtest', () => lvtest);
當我懷著無比期待的心情double r之后:

仔細一看,咦,貌似說dataSource為空,我不是通過fetch獲取到了數(shù)據(jù)并且clone給了this.state.data嗎?怎么會是空呢?
通過你不懈的排查發(fā)現(xiàn),原來是這里的問題:
render() {
return (
<ListView
dataSource={this.state.data}
renderRow={(rowData) => this.renderRow(rowData)}>
</ListView>
);
}
APP一啟動,馬上就要渲染ListView,而fetch數(shù)據(jù)是一個耗時操作,在啟動APP的一瞬間this.state.data肯定是沒有數(shù)據(jù)的!所以在渲染listview時提取數(shù)據(jù),發(fā)現(xiàn)你傳給他的dataSource居然是個空的,那他當然不干,要給你報紅了!
既然知道哪里錯了,那就來解決一下:
render() {
if(!this.state.data){//如果this.state.data沒有數(shù)據(jù)(即網(wǎng)絡請求未完成),則返回一個加載中的文本
return(
<Text>loading...</Text>
);
} else {//當this.state.data有了數(shù)據(jù),則渲染ListView
return (
<ListView
dataSource={this.state.data}
renderRow={(rowData) => this.renderRow(rowData)}>
</ListView>
);
}
}
OK,這個時候再來double r跑一下:

到此,我們就很完美的實現(xiàn)了這樣一個小demo,致于最頂端那個預覽有點擊效果,你可以在renderRow中的布局里,通過包裹<Touchable...>來實現(xiàn),還有很多可玩性等你do it!
下面附上整個【index.android.js】代碼:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
View,
Text,
ListView,
Image
} from 'react-native';
export default class lvtest extends Component {
constructor(props){
super(props);
this.state = {
data: null,
};
}
render() {
if(!this.state.data){
return(
<Text>loading...</Text>
);
} else {
return (
<ListView
dataSource={this.state.data}
renderRow={(rowData) => this.renderRow(rowData)}>
</ListView>
);
}
}
// 生命周期回調(diào)函數(shù)
componentDidMount(){
fetch('http://www.imooc.com/api/teacher?type=4&num=30')
.then((response) => response.json())
.then((jsonData) => {
this.setState({
data: new ListView.DataSource({rowHasChanged: (r1,r2) => r1!==r2 }).cloneWithRows(jsonData.data),
});
})
.catch((error) => {
alert(error);
});
}
// 返回listview的一行
renderRow(rowData){
return(
<View
style = {styles.lvRow}>
<Image
style = { styles.img }
source = { { uri: rowData.picSmall } }/>
<View
style = { styles.textView }>
<Text
style = { styles.textTitle }
numberOfLines = { 1 }>
{ rowData.name }
</Text>
<Text
style = { styles.textContent }>
{ rowData.description }
</Text>
</View>
</View>
)
}
}
const styles = StyleSheet.create({
lvRow: {
flex: 1,
flexDirection: 'row',
padding: 10,
},
textView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 5,
},
textTitle: {
flex: 1,
textAlign: 'center',
color: '#f00',
},
textContent: {
flex: 1,
fontSize: 11,
color: '#000',
textAlign: 'center',
},
img: {
height: 55,
width: 100,
}
});
AppRegistry.registerComponent('lvtest', () => lvtest);