RN FlatList组件

长列表或者无限下拉列表是最常见的应用场景之一。RN 提供的 ListView 组件,在长列表这种数据量大的场景下,性能堪忧。而在最新的 0.43 版本中,提供了 FlatList 组件,或许就是你需要的高性能长列表解决方案。它足以应对大多数的长列表场景。

一、属性说明

 <FlatList      data={showData}
                renderItem={this._renderItem}
                getItemLayout={(data,index)=>(
                  {length:115,offset:115*index,index}
                )}
                refreshing={isRefresh}
                onRefresh={this._onRefresh}
                onEndReached={() => this._onLoadMore()}
                onEndReachedThreshold={0.1}
                ListFooterComponent={this._renderFooter}
                ItemSeparatorComponent={this._separator}
                keyExtractor={item => item.infoId}/>

data

为一个Array,列表的数据来源,数组中的每一项,需要包含 key 值作为唯一标示

keyExtractor

此函数用于为给定的item生成一个不重复的key。Key的作用是使React能够区分同类元素的不同个体,以便在刷新时能够确定其变化的位置,减少重新渲染的开销。若不指定此函数,则默认抽取data中item.key作为key值。若item.key也不存在,则必须使用下列方法添加一个唯一的标识。

keyExtractor={(item, index) => item.infoId}

否则会出下图所示的黄色警告
在这里插入图片描述

renderItem

负责渲染列表中的每个item。

_renderItem({item,index}){
    let {pic,title,runDistance,registTime,userName,price} = item;
    return(
      <View key={index} style={styles.listItem}>
        <Image source={{uri:pic}} style={styles.cheyuanPic}/>
        <View style={styles.rightBox}>
          <Text style={styles.cheyuanTitle} ellipsizeMode='tail' numberOfLines={1}>{title}</Text>
          <Text style={styles.cheyuanJianjie}>{registTime} {runDistance}</Text>
          <Text style={styles.cheyuanJianjie}>{userName}</Text>
          <View style={styles.priceBox}>
            <Text style={styles.cheyuanPrice}>{price}万</Text>
          </View>
        </View>
      </View>
    )
  }

getItemLayout

可选优化项。但是实际测试中,如果不做该项优化,性能会差很多。所以强烈建议做此项优化! 如果不做该项优化,每个列表都需要事先渲染一次,动态地取得其渲染尺寸,然后再真正地渲染到页面中。

如果预先知道列表中的每一项的高度(ITEM_HEIGHT)和其在父组件中的偏移量(offset)和位置(index),就能减少一次渲染。这是很关键的性能优化点

getItemLayout={(data,index)=>(
                  {length:115,offset:115*index,index}
                )}

refreshing、onRefresh组合用于下拉刷新

refreshing:

在等待加载新数据时将此属性设为true,列表就会显示出一个正在加载的符号。

onRefresh:

如果设置了此选项,则会在列表头部添加一个标准的RefreshControl 控件,以便实现“下拉刷新”的功能。同时你需要正确设置refreshing 属性。

onEndReached、onEndReachedThreshold、ListFooterComponent组合用于上拉加载

上拉加载的关键onEndReached,当列表被滚动到距离内容最底部不足onEndReachedThreshold的距离时调用。注意:onEndReachedThreshold的值不是像素单位而是比值,例如,0.5表示距离内容最底部的距离为当前列表可见长度的一半时触发。

我们可以利用官方组件 RefreshControl实现下拉刷新功能,但React Native官方没有提供相应的上拉加载的组件,因此在RN中实现上拉加载比下拉刷新要复杂一点。
虽然没有直接提供上拉加载的组件,不过我们仍可以通过ActivityIndicator以及FlatList的onEndReached与onEndReachedThreshold、ListFooterComponent属性来实现相应效果。

1.ActivityIndicator
这里上拉加载的转圈效果用ActivityIndicator表现。在开始上拉加载的实现之前先介绍一下官方组件ActivityIndicator——加载指示器。ActivityIndicator的使用很简单。
在这里插入图片描述
2.根据以上可以实现一个简单的初始加载页面

renderLoadingView(){
    return(
      <View style={styles.container}>
        <ActivityIndicator animating={true}
                           color='red'
                           size="large"/>
      </View>
    )
  }

3.ListEmptyComponent (当然这个方法是RN0.45才开始有的)
列表为空时渲染该组件。由于我用的0.44,这里就自己控制一下,实现一个列表数据为空的页面

renderEmptyView(){
    return (
      <View style={styles.listEmpty}>
        <Image style={styles.default_img}
               source={{uri: "https://img.58cdn.com.cn/escstatic/fecar/pmuse/chejian_list/momren.png"}} />
        <Text style={styles.default_content}>您暂无符合此筛选条件的车源</Text>
      </View>
    );
  }

4.渲染列表数据

renderData(){
    let {showData,isRefresh} = this.state;
    return(
      <FlatList data={showData}
                renderItem={this._renderItem}
                getItemLayout={(data,index)=>(
                  {length:115,offset:115*index,index}
                )}
                refreshing={isRefresh}
                onRefresh={this._onRefresh}
                onEndReached={() => this._onLoadMore()}
                onEndReachedThreshold={0.1}
                ListFooterComponent={this._renderFooter}
                ItemSeparatorComponent={this._separator}
                keyExtractor={item => item.infoId}/>
    )
  }

5.上拉加载的尾部动画通过FlatList的ListFooterComponent属性,来控制尾部组件的渲染
这里第二页以后尾部才开始展示,没有更多数据时进行提示

_renderFooter(){
    let {hasMorePage,page}=this.state;
    if(hasMorePage && page>=2){
      return(
        <View style={styles.footer}>
          <ActivityIndicator />
          <Text>正在加载更多数据...</Text>
        </View>
      )
    }else if(!hasMorePage && page>=2) {
      return(
        <View style={{height:30,alignItems:'center',justifyContent:'flex-start',}}>
          <Text style={{color:'#999999',fontSize:14,marginTop:5,marginBottom:5,}}>
            没有更多数据了
          </Text>
        </View>
      )
    }else {
      return (
        <View style={styles.footer}>
          <Text></Text>
        </View>
      );
    }
  }

ItemSeparatorComponent属性用来控制分割组件的渲染

_separator(){
    return <View style={{height:1,backgroundColor:'lightgray'}}/>;
  }

最后,附上源码

import React, {PureComponent} from 'react';

import {View, StyleSheet, Text, Image, FlatList, ActivityIndicator, Dimensions} from 'react-native';
let totalWidth=Dimensions.get('window').width;
export default class FlatListDemo extends PureComponent {
  constructor(props){
    super(props);
    this.state={
      showData:[],
      noData:false,
      isRefresh:false,
      isLoading:true,
      page:1,
      listSize:null,
      hasMorePage:true,
      loadingMore:false
    };
    this._onRefresh=this._onRefresh.bind(this);
    this._renderFooter=this._renderFooter.bind(this);
  }
  render(){
    let {isLoading,noData} = this.state;

    if (isLoading){
      return this.renderLoadingView();
    }else if(!noData && !isLoading){
      return this.renderData()
    }else if(noData && !isLoading){
      return this.renderEmptyView()
    }
  }
  _renderItem({item,index}){
    let {pic,title,runDistance,registTime,userName,price} = item;
    return(
      <View key={index} style={styles.listItem}>
        <Image source={{uri:pic}} style={styles.cheyuanPic}/>
        <View style={styles.rightBox}>
          <Text style={styles.cheyuanTitle} ellipsizeMode='tail' numberOfLines={1}>{title}</Text>
          <Text style={styles.cheyuanJianjie}>{registTime} {runDistance}</Text>
          <Text style={styles.cheyuanJianjie}>{userName}</Text>
          <View style={styles.priceBox}>
            <Text style={styles.cheyuanPrice}>{price}万</Text>
          </View>
        </View>
      </View>
    )
  }

  _onRefresh(){
    this.setState({
      isRefresh:true,
      page:1
    },()=>{
      this.getShowData(1,'refresh')
    })
  }

  _onLoadMore(){
    if((this.state.page - 1) * 20 < this.state.listSize && !this.state.loadingMore){
      this.setState({
        loadingMore:true
      },()=>{
        this.getShowData(this.state.page);
      })
    }else if((this.state.page - 1) * 20 >= this.state.listSize && !this.state.loadingMore){
      this.setState({
        hasMorePage:false
      })
    }
  }

  getShowData(page,type){
    fetch(`https://cheapi.58.com/cst/getPriceTop?brand=408844&series=409052&cid=304&pageIndex=${page}`)
      .then(res=>res.json())
      .then(data=>{
        if(data.status===0 && data.result.infoMap.listSize!==0){
          let dateAry = data.result.infoMap.InfoList.map(item => {
            return item.infoMap;
          });
          if(type==='refresh'){
            this.setState({
              showData:[...dateAry],
              page:this.state.page+1,
              isRefresh:false,
              isLoading:false,
              noData:false,
              loadingMore:false,
              listSize:data.result.infoMap.listSize
            })
          }else {
            this.setState({
              showData:[...this.state.showData,...dateAry],
              page:this.state.page+1,
              isRefresh:false,
              isLoading:false,
              noData:false,
              loadingMore:false,
              listSize:data.result.infoMap.listSize
            })
          }
        }else if(data.status===0 && data.result.infoMap.listSize===0){
          this.setState({
            noData:true,
            showData:[],
            page:this.state.page+1,
            isRefresh:false,
            isLoading:false,
            loadingMore:false,
            listSize:data.result.infoMap.listSize
          })
        }
      })
  }

  componentDidMount(){
    this.getShowData(1);
  }

  renderLoadingView(){
    return(
      <View style={styles.container}>
        <ActivityIndicator animating={true}
                           color='red'
                           size="large"/>
      </View>
    )
  }

  renderData(){
    let {showData,isRefresh} = this.state;
    return(
      <FlatList data={showData}
                renderItem={this._renderItem}
                getItemLayout={(data,index)=>(
                  {length:115,offset:115*index,index}
                )}
                refreshing={isRefresh}
                onRefresh={this._onRefresh}
                onEndReached={() => this._onLoadMore()}
                onEndReachedThreshold={0.1}
                ListFooterComponent={this._renderFooter}
                ItemSeparatorComponent={this._separator}
                keyExtractor={item => item.infoId}/>
    )
  }

  renderEmptyView(){
    return (
      <View style={styles.listEmpty}>
        <Image style={styles.default_img}
               source={{uri: "https://img.58cdn.com.cn/escstatic/fecar/pmuse/chejian_list/momren.png"}} />
        <Text style={styles.default_content}>您暂无符合此筛选条件的车源</Text>
      </View>
    );
  }

  _renderFooter(){
    let {hasMorePage,page}=this.state;
    if(hasMorePage && page>=2){
      return(
        <View style={styles.footer}>
          <ActivityIndicator />
          <Text>正在加载更多数据...</Text>
        </View>
      )
    }else if(!hasMorePage && page>=2) {
      return(
        <View style={{height:30,alignItems:'center',justifyContent:'flex-start',}}>
          <Text style={{color:'#999999',fontSize:14,marginTop:5,marginBottom:5,}}>
            没有更多数据了
          </Text>
        </View>
      )
    }else {
      return (
        <View style={styles.footer}>
          <Text></Text>
        </View>
      );
    }
  }

  _separator(){
    return <View style={{height:1,backgroundColor:'lightgray'}}/>;
  }
}

const styles = StyleSheet.create({
  container:{
    flex:1,
    width:totalWidth,
    justifyContent: 'center',
    alignItems:'center',
    backgroundColor: '#F5FCFF'
  },
  listItem:{
    height:115,
    width:totalWidth,
    backgroundColor: '#FFFFFF',
    paddingLeft:15,
    paddingTop:15,
    paddingRight:15,
    flexDirection: 'row'
  },
  cheyuanPic:{
    height: 80,
    width:105
  },
  rightBox:{
    flex:1,
    marginLeft:10,
    height:80
  },
  cheyuanTitle:{
    fontFamily: 'PingFangSC-Semibold',
    fontSize: 14,
    color: '#333333',
  },
  cheyuanJianjie:{
    fontFamily: 'PingFangSC-Regular',
    fontSize: 12,
    color: '#999999'
  },
  priceBox:{
    marginTop:5
  },
  cheyuanPrice:{
    fontFamily: 'PingFangSC-Semibold',
    fontSize: 13,
    color: '#FF552E'
  },
  footer:{
    flexDirection:'row',
    height:24,
    justifyContent:'center',
    alignItems:'center',
    marginBottom:10,
  },
  listEmpty:{
    flex:1,
    width:totalWidth,
    justifyContent: 'center',
    alignItems:'center',
  },
  default_img:{
    width:90,
    height:90,
    marginBottom:20
  },
  default_content:{
    fontFamily: 'PingFangSC-Regular',
    fontSize: 15,
    height: 21,
    color: '#CCCCCC',
    textAlign: 'center'
  }
});



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值