ReactNative网络fetch数据并展示在listview中

看完全文并且do it你将收获:

  1. fetch获取网络数据的基本用法
  2. ListView的基本用法
  3. RN中组件的生命周期(有可能)

类似的实现其实有很多朋友已经写过了,我也看了几篇类似博客

这篇《react-native中listview获取豆瓣书本信息并展示出来》确实是一个值得学习的例子,但作者只是一步一步贴代码,没有很好的细讲

这篇《React Native从网络拉取数据并填充列表》写的不错,值得一看,不过listview没有图片,效果看起来没有我的有逼格(傲娇脸)

来,先看下最终我们要实现一个怎么样的效果吧:


实现效果

好,正题开始:

要展示出这样一个listview的效果,首先需要有数据源,我这里私藏了两个能返回json(带有图片地址)的url:

url最后的数字是返回数据的数量,可以自己修改
比如第一个url将返回30条数据,第二个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长啥样,在浏览器中输入url后:

我使用的是chrome浏览器,通过插件“JSON-handle”将json格式化并展示了出来,很友好的效果,其他浏览器也有类似插件,可自行搞定

在浏览器中看到的json数据

果然是好长一段的json!我们需要的内容在json>data中,对比预览图得知需要的数据段为:name、picSmall(当然用小图啦)、description三个。

OK,既然有了数据源,那么接下来要做的就是通过网络请求去获取数据

RN中的网络请求通过Fetch进行,最简单的fetch写法是传入一个url参数:

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

通过这一句就成功发起了一个网络请求,当然fetch方法还有第二个可选参数,这个参数用于传入一些HTTP请求的参数如:headers、请求方式(GET/POST)等
援引RN中文网fetch一段示例代码:

fetch('https://mywebsite.com/endpoint/', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    firstParam: 'yourValue',
    secondParam: 'yourOtherValue',
  })
})

仅用于获取简单的数据则完全可以忽略第二个参数

在用fetch请求网络成功后,会返回的是一个Promise对象,用于检索我们需要的数据,但我们需要的是一个json对象来便于处理数据,那么怎样将这个Promise对象转换成json对象呢?fetch有非常方便的链式调用方法来搞定:

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

    //.then()中传入一个回调函数,该回调函数接收fetch返回的对象
    //你可以在这个时候将Promise对象转换成json对象:response.json()
    //转换成json对象后return,给下一步的.then处理

没错!.then可以一直接下去!并且每一个.then都接收一个回调函数,这个回调函数都接收上一个.then返回的数据,这样做数据处理岂不是美滋滋!

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是一个对象数组
      });
    })                           //你后面还可以继续疯狂的接.then并且传递数据,尽管试试吧!
    .catch((error) => {          //注意尾部添加异常回调
      alert(error);
    });
  }
//上一个.then的回调函数返回的数据,由下一个.then的回调函数接收

为啥把fetch写在componentDidMount()中呢,因为这是一个RN中组件的生命周期回调函数,会在组件渲染后回调
关于更多组件生命周期戳React Native 中组件的生命周期

那么我们最终得到的this.state.data到底长啥样的?根据json结构来分析一下,json.data是一个对象数组,现在赋给了this.state.data,那么this.state.data应该就是一个对象数组,我们来检测一下:

  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中的数据

很好,这说明data是一个对象数组,通过数组取值方法取到了值,结构看json那边,这样就很好的可以取到其中的值

data中的第一条数据
关于更多详细的fetch介绍可以自行G/B,也墙裂建议认真看看、理解fetch

拿到了数据,接下来开始使用listview了

首先得导入ListView组件(我很容易忘记导入组件),接下来就是把它写在布局中
使用<ListView>的时候,有两个必须参数:dataSource、renderRow

<ListView
  dataSource={}      //必须写此属性并且传入dataSource对象
  renderRow={}>      //必须写此属性并且传入一个返回component的回调函数
</ListView>

这个很容易理解,listview要展示数据,必须得有数据源(dataSource),之后listview必须知道他每一行长啥样,他才能去渲染(renderRow)

dataSource

dataSource是ListView下的一个类,新建一个dataSource:

let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
//DataSource()可以接收四种参数
//rowHasChanged就是渲染每一行的依据,r1是上一行的数据,r2是下一行的数据
//此处定义:r1和r2不同时,需要渲染新的一行

关于DataSource可接收四种参数:

  1. getRowData(dataBlob, sectionID, rowID):表明我们将以何种方式从dataBlob(数据源)中提取出rowData,sectionID用于指定每一个section的标题名(在renderRow,renderHeader等方法中会默认拆开并作为参数传入)
  2. getSectionHeaderData(dataBlob, sectionID):表明我们将以何种方式从dataBlob(数据源)中提取出HeaderData。HeaderData用于给每一个sectionHeader赋值
  3. rowHasChanged(prevRowData, nextRowData):指定我们更新row的策略,一般来说都是prevRowData和nextRowData不相等时更新row
  4. sectionHeaderHasChanged(prevSectionData, nextSectionData):指定我们更新sectionHeader的策略,一般如果用到sectionData的时候才指定

引自:React-Native组件用法详解之ListView
==========本次Demo实现只需要用到rowHasChanged

墙裂建议认真学习更多listview详细内容

这个时候我们创建出来的ds还并不拥有任何数据,在传给listview之前还要进行一个步骤:

let lvData = ds.cloneWithRows(this.state.data);
//可以粗暴理解为:将this.state.data克隆到lvData,只有经过这一步的数据才能给listview使用

我们希望网络请求到数据时立即进行cloneWithRows这一步,这样this.state.data在网络请求完成后就直接被赋值成为可以传递给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属性接收一个“返回每一行的组件的回调函数”,该回调函数接收一个分配给该行的数据,在本例中即为json.data中的子项
OK,既然知道renderRow需要啥,并且知道每一行长啥样:

listview的一行的样式

我们就可以根据这个样式来写一个需要的回调函数:

export default class lvtest extends Component {

………………
  // 返回listview的一行
  renderRow(rowData){//参数为接收的每一行的数据,理解:数组data的子项
    return(
      <View                                //最外层包裹的View
        style = {styles.lvRow}>
          <Image                           //左侧显示的图片
            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不能直接当做数组使用,他是一个dataSource对象
          </Text>
          <Text                //详细内容文本
            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,我相信大家都很容易看明白这个布局的实现,至此我们所有的代码如下:(还有个坑呢,继续看完!)

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>
    );
  }


// 生命周期回调函数,在其中进行网络请求
  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获取到了数据并且clone给了this.state.data吗?怎么会是空呢?

通过你不懈的排查发现,原来是这里的问题:
  render() {
    return (
      <ListView
        dataSource={this.state.data}
        renderRow={(rowData) => this.renderRow(rowData)}>
      </ListView>
    );
  }

APP一启动,马上就要渲染ListView,而fetch数据是一个耗时操作,在启动APP的一瞬间this.state.data肯定是没有数据的!所以在渲染listview时提取数据,发现你传给他的dataSource居然是个空的,那他当然不干,要给你报红了!

既然知道哪里错了,那就来解决一下:

  render() {
    if(!this.state.data){//如果this.state.data没有数据(即网络请求未完成),则返回一个加载中的文本
      return(
          <Text>loading...</Text>
      );
    } else {//当this.state.data有了数据,则渲染ListView
      return (
        <ListView
          dataSource={this.state.data}
          renderRow={(rowData) => this.renderRow(rowData)}>
        </ListView>
      );
    }
  }

OK,这个时候再来double r跑一下:

究极形态

到此,我们就很完美的实现了这样一个小demo,致于最顶端那个预览有点击效果,你可以在renderRow中的布局里,通过包裹<Touchable...>来实现,还有很多可玩性等你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>
      );
    }
  }

// 生命周期回调函数
  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);

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容