ReactNative網(wǎng)絡(luò)fetch數(shù)據(jù)并展示在listview中

看完全文并且do it你將收獲:

  1. fetch獲取網(wǎng)絡(luò)數(shù)據(jù)的基本用法
  2. ListView的基本用法
  3. RN中組件的生命周期(有可能)

類似的實(shí)現(xiàn)其實(shí)有很多朋友已經(jīng)寫過了,我也看了幾篇類似博客

這篇《react-native中l(wèi)istview獲取豆瓣書本信息并展示出來》確實(shí)是一個(gè)值得學(xué)習(xí)的例子,但作者只是一步一步貼代碼,沒有很好的細(xì)講

這篇《React Native從網(wǎng)絡(luò)拉取數(shù)據(jù)并填充列表》寫的不錯(cuò),值得一看,不過listview沒有圖片,效果看起來沒有我的有逼格(傲嬌臉)

來,先看下最終我們要實(shí)現(xiàn)一個(gè)怎么樣的效果吧:


實(shí)現(xiàn)效果

好,正題開始:

要展示出這樣一個(gè)listview的效果,首先需要有數(shù)據(jù)源,我這里私藏了兩個(gè)能返回json(帶有圖片地址)的url:

url最后的數(shù)字是返回?cái)?shù)據(jù)的數(shù)量,可以自己修改
比如第一個(gè)url將返回30條數(shù)據(jù),第二個(gè)url將返回3條

  1. http://www.imooc.com/api/teacher?type=4&num=30
  2. https://api.douban.com/v2/book/search?q=react&count=3

我們先看一看返回的json長(zhǎng)啥樣,在瀏覽器中輸入url后:

我使用的是chrome瀏覽器,通過插件“JSON-handle”將json格式化并展示了出來,很友好的效果,其他瀏覽器也有類似插件,可自行搞定

在瀏覽器中看到的json數(shù)據(jù)

果然是好長(zhǎng)一段的json!我們需要的內(nèi)容在json>data中,對(duì)比預(yù)覽圖得知需要的數(shù)據(jù)段為:name、picSmall(當(dāng)然用小圖啦)、description三個(gè)。

OK,既然有了數(shù)據(jù)源,那么接下來要做的就是通過網(wǎng)絡(luò)請(qǐng)求去獲取數(shù)據(jù)

RN中的網(wǎng)絡(luò)請(qǐng)求通過Fetch進(jìn)行,最簡(jiǎn)單的fetch寫法是傳入一個(gè)url參數(shù):

fetch('http://www.imooc.com/api/teacher?type=4&num=30');

通過這一句就成功發(fā)起了一個(gè)網(wǎng)絡(luò)請(qǐng)求,當(dāng)然fetch方法還有第二個(gè)可選參數(shù),這個(gè)參數(shù)用于傳入一些HTTP請(qǐng)求的參數(shù)如:headers、請(qǐng)求方式(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',
  })
})

僅用于獲取簡(jiǎn)單的數(shù)據(jù)則完全可以忽略第二個(gè)參數(shù)

在用fetch請(qǐng)求網(wǎng)絡(luò)成功后,會(huì)返回的是一個(gè)Promise對(duì)象,用于檢索我們需要的數(shù)據(jù),但我們需要的是一個(gè)json對(duì)象來便于處理數(shù)據(jù),那么怎樣將這個(gè)Promise對(duì)象轉(zhuǎn)換成json對(duì)象呢?fetch有非常方便的鏈?zhǔn)秸{(diào)用方法來搞定:

    fetch('https://mywebsite.com/endpoint/')
    .then((response) => response.json());//response就是fetch返回的Promise對(duì)象

    //.then()中傳入一個(gè)回調(diào)函數(shù),該回調(diào)函數(shù)接收fetch返回的對(duì)象
    //你可以在這個(gè)時(shí)候?qū)romise對(duì)象轉(zhuǎn)換成json對(duì)象:response.json()
    //轉(zhuǎn)換成json對(duì)象后return,給下一步的.then處理

沒錯(cuò)!.then可以一直接下去!并且每一個(gè).then都接收一個(gè)回調(diào)函數(shù),這個(gè)回調(diào)函數(shù)都接收上一個(gè).then返回的數(shù)據(jù),這樣做數(shù)據(jù)處理豈不是美滋滋!

ok,既然這樣,那我們?cè)谙乱粋€(gè).then中接收到這個(gè)由上一個(gè).then返回的json對(duì)象,并將他設(shè)置給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是一個(gè)對(duì)象數(shù)組
      });
    })                           //你后面還可以繼續(xù)瘋狂的接.then并且傳遞數(shù)據(jù),盡管試試吧!
    .catch((error) => {          //注意尾部添加異常回調(diào)
      alert(error);
    });
  }
//上一個(gè).then的回調(diào)函數(shù)返回的數(shù)據(jù),由下一個(gè).then的回調(diào)函數(shù)接收

為啥把fetch寫在componentDidMount()中呢,因?yàn)檫@是一個(gè)RN中組件的生命周期回調(diào)函數(shù),會(huì)在組件渲染后回調(diào)
關(guān)于更多組件生命周期戳React Native 中組件的生命周期

那么我們最終得到的this.state.data到底長(zhǎng)啥樣的?根據(jù)json結(jié)構(gòu)來分析一下,json.data是一個(gè)對(duì)象數(shù)組,現(xiàn)在賦給了this.state.data,那么this.state.data應(yīng)該就是一個(gè)對(duì)象數(shù)組,我們來檢測(cè)一下:

  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ù)據(jù)

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

data中的第一條數(shù)據(jù)
關(guān)于更多詳細(xì)的fetch介紹可以自行G/B,也墻裂建議認(rèn)真看看、理解fetch

拿到了數(shù)據(jù),接下來開始使用listview了

首先得導(dǎo)入ListView組件(我很容易忘記導(dǎo)入組件),接下來就是把它寫在布局中
使用<ListView>的時(shí)候,有兩個(gè)必須參數(shù):dataSource、renderRow

<ListView
  dataSource={}      //必須寫此屬性并且傳入dataSource對(duì)象
  renderRow={}>      //必須寫此屬性并且傳入一個(gè)返回component的回調(diào)函數(shù)
</ListView>

這個(gè)很容易理解,listview要展示數(shù)據(jù),必須得有數(shù)據(jù)源(dataSource),之后listview必須知道他每一行長(zhǎng)啥樣,他才能去渲染(renderRow)

dataSource

dataSource是ListView下的一個(gè)類,新建一個(gè)dataSource:

let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
//DataSource()可以接收四種參數(shù)
//rowHasChanged就是渲染每一行的依據(jù),r1是上一行的數(shù)據(jù),r2是下一行的數(shù)據(jù)
//此處定義:r1和r2不同時(shí),需要渲染新的一行

關(guān)于DataSource可接收四種參數(shù):

  1. getRowData(dataBlob, sectionID, rowID):表明我們將以何種方式從dataBlob(數(shù)據(jù)源)中提取出rowData,sectionID用于指定每一個(gè)section的標(biāo)題名(在renderRow,renderHeader等方法中會(huì)默認(rèn)拆開并作為參數(shù)傳入)
  2. getSectionHeaderData(dataBlob, sectionID):表明我們將以何種方式從dataBlob(數(shù)據(jù)源)中提取出HeaderData。HeaderData用于給每一個(gè)sectionHeader賦值
  3. rowHasChanged(prevRowData, nextRowData):指定我們更新row的策略,一般來說都是prevRowData和nextRowData不相等時(shí)更新row
  4. sectionHeaderHasChanged(prevSectionData, nextSectionData):指定我們更新sectionHeader的策略,一般如果用到sectionData的時(shí)候才指定

引自:React-Native組件用法詳解之ListView
==========本次Demo實(shí)現(xiàn)只需要用到rowHasChanged

墻裂建議認(rèn)真學(xué)習(xí)更多l(xiāng)istview詳細(xì)內(nèi)容

這個(gè)時(shí)候我們創(chuàng)建出來的ds還并不擁有任何數(shù)據(jù),在傳給listview之前還要進(jìn)行一個(gè)步驟:

let lvData = ds.cloneWithRows(this.state.data);
//可以粗暴理解為:將this.state.data克隆到lvData,只有經(jīng)過這一步的數(shù)據(jù)才能給listview使用

我們希望網(wǎng)絡(luò)請(qǐng)求到數(shù)據(jù)時(shí)立即進(jìn)行cloneWithRows這一步,這樣this.state.data在網(wǎng)絡(luò)請(qǐng)求完成后就直接被賦值成為可以傳遞給listview的對(duì)象:

    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屬性接收一個(gè)“返回每一行的組件的回調(diào)函數(shù)”,該回調(diào)函數(shù)接收一個(gè)分配給該行的數(shù)據(jù),在本例中即為json.data中的子項(xiàng)
OK,既然知道renderRow需要啥,并且知道每一行長(zhǎng)啥樣:

listview的一行的樣式

我們就可以根據(jù)這個(gè)樣式來寫一個(gè)需要的回調(diào)函數(shù):

export default class lvtest extends Component {

………………
  // 返回listview的一行
  renderRow(rowData){//參數(shù)為接收的每一行的數(shù)據(jù),理解:數(shù)組data的子項(xiàng)
    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                            //標(biāo)題
            style = { styles.textTitle }
            numberOfLines = { 1 }>         //只顯示一行
            { rowData.name }               //取rowData中的name字段,理解:data[某行].name
                                  //注意:this.state.data不能直接當(dāng)做數(shù)組使用,他是一個(gè)dataSource對(duì)象
          </Text>
          <Text                //詳細(xì)內(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,我相信大家都很容易看明白這個(gè)布局的實(shí)現(xiàn),至此我們所有的代碼如下:(還有個(gè)坑呢,繼續(xù)看完!)

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ù),在其中進(jìn)行網(wǎng)絡(luò)請(qǐ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);

當(dāng)我懷著無比期待的心情double r之后:

瘋狂報(bào)錯(cuò)

仔細(xì)一看,咦,貌似說dataSource為空,我不是通過fetch獲取到了數(shù)據(jù)并且clone給了this.state.data嗎?怎么會(huì)是空呢?

通過你不懈的排查發(fā)現(xiàn),原來是這里的問題:
  render() {
    return (
      <ListView
        dataSource={this.state.data}
        renderRow={(rowData) => this.renderRow(rowData)}>
      </ListView>
    );
  }

APP一啟動(dòng),馬上就要渲染ListView,而fetch數(shù)據(jù)是一個(gè)耗時(shí)操作,在啟動(dòng)APP的一瞬間this.state.data肯定是沒有數(shù)據(jù)的!所以在渲染listview時(shí)提取數(shù)據(jù),發(fā)現(xiàn)你傳給他的dataSource居然是個(gè)空的,那他當(dāng)然不干,要給你報(bào)紅了!

既然知道哪里錯(cuò)了,那就來解決一下:

  render() {
    if(!this.state.data){//如果this.state.data沒有數(shù)據(jù)(即網(wǎng)絡(luò)請(qǐng)求未完成),則返回一個(gè)加載中的文本
      return(
          <Text>loading...</Text>
      );
    } else {//當(dāng)this.state.data有了數(shù)據(jù),則渲染ListView
      return (
        <ListView
          dataSource={this.state.data}
          renderRow={(rowData) => this.renderRow(rowData)}>
        </ListView>
      );
    }
  }

OK,這個(gè)時(shí)候再來double r跑一下:

究極形態(tài)

到此,我們就很完美的實(shí)現(xiàn)了這樣一個(gè)小demo,致于最頂端那個(gè)預(yù)覽有點(diǎn)擊效果,你可以在renderRow中的布局里,通過包裹<Touchable...>來實(shí)現(xiàn),還有很多可玩性等你do it!

下面附上整個(gè)【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);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,382評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,616評(píng)論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,112評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,355評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,869評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,727評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,928評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,165評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評(píng)論 1 282
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,585評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,892評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容