长列表或者无限下拉列表是最常见的应用场景之一。RN 提供的 ListView 组件,在长列表这种数据量大的场景下,性能堪忧。而在最新的 0.43 版本中,提供了 FlatList 组件,或许就是你需要的高性能长列表解决方案。它足以应对大多数的长列表场景。
(
{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}/>
为一个Array,列表的数据来源,数组中的每一项,需要包含 key 值作为唯一标示
此函数用于为给定的item生成一个不重复的key。Key的作用是使React能够区分同类元素的不同个体,以便在刷新时能够确定其变化的位置,减少重新渲染的开销。若不指定此函数,则默认抽取data中item.key作为key值。若item.key也不存在,则必须使用下列方法添加一个唯一的标识。
keyExtractor={(item, index) => item.infoId}
否则会出下图所示的黄色警告
负责渲染列表中的每个item。
_renderItem({item,index}){
let {pic,title,runDistance,registTime,userName,price} = item;
return(
{title}
{registTime} {runDistance}
{userName}
{price}万
)
}
可选优化项。但是实际测试中,如果不做该项优化,性能会差很多。所以强烈建议做此项优化! 如果不做该项优化,每个列表都需要事先渲染一次,动态地取得其渲染尺寸,然后再真正地渲染到页面中。
如果预先知道列表中的每一项的高度(ITEM_HEIGHT)和其在父组件中的偏移量(offset)和位置(index),就能减少一次渲染。这是很关键的性能优化点
getItemLayout={(data,index)=>(
{length:115,offset:115*index,index}
)}
refreshing:
在等待加载新数据时将此属性设为true,列表就会显示出一个正在加载的符号。
onRefresh:
如果设置了此选项,则会在列表头部添加一个标准的RefreshControl 控件,以便实现“下拉刷新”的功能。同时你需要正确设置refreshing 属性。
上拉加载的关键onEndReached,当列表被滚动到距离内容最底部不足onEndReachedThreshold的距离时调用。注意:onEndReachedThreshold的值不是像素单位而是比值,例如,0.5表示距离内容最底部的距离为当前列表可见长度的一半时触发。
我们可以利用官方组件 RefreshControl实现下拉刷新功能,但React Native官方没有提供相应的上拉加载的组件,因此在RN中实现上拉加载比下拉刷新要复杂一点。
虽然没有直接提供上拉加载的组件,不过我们仍可以通过ActivityIndicator以及FlatList的onEndReached与onEndReachedThreshold、ListFooterComponent属性来实现相应效果。
1.ActivityIndicator
这里上拉加载的转圈效果用ActivityIndicator表现。在开始上拉加载的实现之前先介绍一下官方组件ActivityIndicator——加载指示器。ActivityIndicator的使用很简单。
2.根据以上可以实现一个简单的初始加载页面
renderLoadingView(){
return(
)
}
3.ListEmptyComponent (当然这个方法是RN0.45才开始有的)
列表为空时渲染该组件。由于我用的0.44,这里就自己控制一下,实现一个列表数据为空的页面
renderEmptyView(){
return (
您暂无符合此筛选条件的车源
);
}
4.渲染列表数据
renderData(){
let {showData,isRefresh} = this.state;
return(
(
{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(
正在加载更多数据...
)
}else if(!hasMorePage && page>=2) {
return(
没有更多数据了
)
}else {
return (
);
}
}
_separator(){
return ;
}
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(
{title}
{registTime} {runDistance}
{userName}
{price}万
)
}
_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(
)
}
renderData(){
let {showData,isRefresh} = this.state;
return(
(
{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 (
您暂无符合此筛选条件的车源
);
}
_renderFooter(){
let {hasMorePage,page}=this.state;
if(hasMorePage && page>=2){
return(
正在加载更多数据...
)
}else if(!hasMorePage && page>=2) {
return(
没有更多数据了
)
}else {
return (
);
}
}
_separator(){
return ;
}
}
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'
}
});