在过去的一年中React Native经历了从v0.40到v0.52的十几次的版本迭代,可以看到,特别是0.50之后,React Native的组件库在不断地壮大,React Native也正在越来越稳定。
随着版本的升级,React Native引进了一些新的组件中,如FlatList、SectionList等具有更高性能的列表组件,也有与时俱进的用于适配全屏幕的SafeAreaView组件,同时一些性能比较差、无法适应React Native发展的一些老的组件也逐渐被抛弃,如:ListView、Navigator等组件。
下面是一张说明图,来自于网络:
FlatList简介
总所周知,为了实现列表的效果,React Native提供了ListView组件,并且通过对ListView进行简单的封装,ListView还可以实现下拉刷新和上拉加载的功能。
但是如果对ListView比较了解的同学都会发现,ListView的性能是非常差的,所以React Native在0.43版本推出了FlatList,FlatList自带上拉下拉的功能,用于替换ListView。
FlatList功能简介
FlatList支持如下功能:
- 完全跨平台;
- 支持水平布局模式;
- 行组件显示或隐藏时可配置回调事件;
- 支持单独的头部组件;
- 支持单独的尾部组件;
- 支持自定义行间分隔线;
- 支持下拉刷新;
- 支持上拉加载更多;
- 支持跳转到指定行(ScrollToIndex)。
注:如果需要实现分组/类/区的效果,请使用<SectionList>
组件。
FlatList实例
FlatList的使用也非常的简单,下面是
<FlatList
data={[{key: 'a'}, {key: 'b'}]}
renderItem={({item}) => <Text>{item.key}</Text>}
/>
- 1
- 2
- 3
- 4
可以看出,FlatList跟之前的ListView很像,但是其中少了dataSource,我们只需要传递数据,其它的都交给FlatList处理好了。之前ListView组件的使用如下,让我们对比来看:
<ListView
style={styles.listView}
dataSource={this.state.dataSource}
renderRow={this.renderCell.bind(this)}
/>
- 1
- 2
- 3
- 4
- 5
属性说明
- ItemSeparatorComponent
行与行之间的分隔线组件,不会出现在第一行之前和最后一行之后。 - ListEmptyComponent
列表为空时渲染该组件,可以是React Component,也可以是一个render函数, 或者渲染好的element。 - ListFooterComponent
尾部组件。 - ListHeaderComponent
头部组件。 - columnWrapperStyle
如果设置了多列布局(即将numColumns
值设为大于1的整数),则可以额外指定此样式作用在每行容器上。 - data
为了简化起见,data属性目前只支持普通数组,如果需要使用其他特殊数据结构,例如immutable数组,请使用更底层的VirtualizedList组件。 - extraData
如果有除data以外的数据作用在列表中(不论是用在renderItem
还是Header或者Footer中),请在此属性中指定作用的对象。同时此数据在修改时也需要先修改其引用地址(比如先复制到一个新的Object或者数组中),然后再修改其值,否则界面很可能不会刷新。 - getItem
获取每个Item。 - getItemCount
获取Item个数。 - getItemLayout
getItemLayout是一个可选的优化,用于避免动态测量内容尺寸带来的开销,不过前提是你可以提前知道内容的高度。也就是说,如果你的行高是固定的,那么getItemLayout用起来就既高效又简单,类似下面这样:getItemLayout={(data, index) => ( {length: 行高, offset: 行高 * index, index} )}
注意:如果你指定了SeparatorComponent,请把分隔线的尺寸也考虑到offset的计算之中。 - horizontal
当此属性设置为true时,则变为水平布局模式。 - initialNumToRender
指定一开始渲染的元素数量,最好刚刚够填满一个屏幕,这样保证了用最短的时间给用户呈现可见的内容。注意这第一批次渲染的元素不会在滑动过程中被卸载,这样是为了保证用户执行返回顶部的操作时,不需要重新渲染首批元素。 - initialScrollIndex
指定渲染开始的item index。 - inverted
翻转滚动方向,实质是将scale变换设置为-1。 - keyExtractor
此函数用于为给定的item生成一个不重复的key。Key的作用是使React能够区分同类元素的不同个体,以便在刷新时能够确定其变化的位置,减少重新渲染的开销。若不指定此函数,则默认抽取item.key作为key值,若item.key也不存在,则使用数组下标。 - legacyImplementation
设置为true则使用旧的ListView的实现。 - numColumns
多列布局只能在非水平模式下使用,即必须是horizontal={false}
时。此时组件内元素会从左到右从上到下按Z字形排列,类似启用了flexWrap的布局,且组件内元素必须是等高的,暂时还无法支持瀑布流布局。 - onEndReached
当列表被滚动到距离内容最底部不足onEndReachedThreshold的距离时调用。 - onEndReachedThreshold
决定当距离内容最底部还有多远时触发onEndReached回调,注意此参数是一个比值而非像素单位。比如,0.5表示距离内容最底部的距离为当前列表可见长度的一半时触发。 - onRefresh
如果设置了此选项,则会在列表头部添加一个标准的RefreshControl
控件,以便实现“下拉刷新”的功能,同时你需要正确设置refreshing
属性。 - onViewableItemsChanged
在可见行元素变化时调用,可见范围和变化频率等参数的配置请设置viewabilityconfig属性。 - refreshing
在等待加载新数据时将此属性设为true,列表就会显示出一个正在加载的符号。 - renderItem
根据行数据data,渲染每一行的组件。 - viewabilityConfig
请参考ViewabilityHelper的源码来了解具体的配置。
方法
- scrollToEnd
滚动到底部,如果不设置getItemLayout属性的话,可能会比较卡。 - scrollToIndex
滚动到指定index的item,如果不设置getItemLayout属性的话,无法跳转到当前可视区域以外的位置。 - scrollToItem
滚动到指定item,如果不设置getItemLayout属性的话,可能会比较卡。 - scrollToOffset
滚动指定距离。
复杂实例
下面是一个复杂的实例,效果如图:
源码如下:
import React,{Component} from 'react';
import {
View,Text,FlatList
} from 'react-native';
const data = [
{key:'a',text:'jiangzhixi'},
{key:'b',text:'jiangdonlin'},
{key:'c',text:'huqianlong'},
];
const dataone = [{key: 'a',title:'aa'}, {key: 'b',title:'ba'}];
class Separator extends Component{
render(){
return (
<Text> ---</Text>
);
}
}
export default class FlatListView extends Component{
renderItem(item){
return (
<View key={item}>
<Text key={item}>{item.text}</Text>
</View>
)
}
renderSeparator(){
return <Separator />;
}
renderHeadComp(){
return <Text>我是头</Text>
}
renderEndComp(){
return <Text>我是end</Text>
}
keyExtractor(item, index){
console.log(item);//这里的item就是data里的每一项
console.log(index);//index就是每一项的索引
}
render(){
return (
<View>
<FlatList
data={data}
horizontal={false}
initialNumToRender={3}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeadComp}
ListFooterComponent={this.renderEndComp}
refreshing={true}
renderItem={({item}) => this.renderItem(item)}
keyExtractor={this.keyExtractor}
/>
</View>
);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
FlatList项目实战
首先我们看一下实现的效果,这也是我之前《React Native移动开发实战》中的一个项目。
上拉加载下拉刷新控件封装
源代码如下:
/**
* 基于FlatList实现的上拉加载和下拉刷新组件
*/
import React, {PureComponent} from 'react'
import {View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, ViewPropTypes} from 'react-native'
export const RefreshState = {
Idle: 0,
HeaderRefreshing: 1,
FooterRefreshing: 2,
NoMoreData: 3,
Failure: 4,
EmptyData: 5,
}
const DEBUG = false
const log = (text: string) => {DEBUG && console.log(text)}
type Props = {
refreshState: number,
onHeaderRefresh: Function,
onFooterRefresh?: Function,
data: Array<any>,
footerContainerStyle?: ViewPropTypes.style,
footerTextStyle?: ViewPropTypes.style,
listRef?: any,
footerRefreshingText?: string,
footerFailureText?: string,
footerNoMoreDataText?: string,
footerEmptyDataText?: string,
renderItem: Function,
}
type State = {
}
export default class RefreshListView extends PureComponent<Props, State> {
static defaultProps = {
footerRefreshingText: '数据加载中…',
footerFailureText: '点击重新加载',
footerNoMoreDataText: '已加载全部数据',
footerEmptyDataText: '暂时没有相关数据',
}
componentWillReceiveProps(nextProps: Props) {
log('[RefreshListView] RefreshListView componentWillReceiveProps ' + nextProps.refreshState)
}
componentDidUpdate(prevProps: Props, prevState: State) {
log('[RefreshListView] RefreshListView componentDidUpdate ' + prevProps.refreshState)
}
onHeaderRefresh = () => {
log('[RefreshListView] onHeaderRefresh')
if (this.shouldStartHeaderRefreshing()) {
log('[RefreshListView] onHeaderRefresh')
this.props.onHeaderRefresh(RefreshState.HeaderRefreshing)
}
}
onEndReached = (info: {distanceFromEnd: number}) => {
log('[RefreshListView] onEndReached ' + info.distanceFromEnd)
if (this.shouldStartFooterRefreshing()) {
log('[RefreshListView] onFooterRefresh')
this.props.onFooterRefresh && this.props.onFooterRefresh(RefreshState.FooterRefreshing)
}
}
shouldStartHeaderRefreshing = () => {
log('[RefreshListView] shouldStartHeaderRefreshing')
if (this.props.refreshState == RefreshState.HeaderRefreshing ||
this.props.refreshState == RefreshState.FooterRefreshing) {
return false
}
return true
}
shouldStartFooterRefreshing = () => {
log('[RefreshListView] shouldStartFooterRefreshing')
let {refreshState, data} = this.props
if (data.length == 0) {
return false
}
return (refreshState == RefreshState.Idle)
}
render() {
log('[RefreshListView] render')
let {renderItem, ...rest} = this.props
return (
<FlatList
ref={this.props.listRef}
onEndReached={this.onEndReached}
onRefresh={this.onHeaderRefresh}
refreshing={this.props.refreshState == RefreshState.HeaderRefreshing}
ListFooterComponent={this.renderFooter}
onEndReachedThreshold={0.1}
renderItem={renderItem}
{...rest}
/>
)
}
renderFooter = () => {
let footer = null
let footerContainerStyle = [styles.footerContainer, this.props.footerContainerStyle]
let footerTextStyle = [styles.footerText, this.props.footerTextStyle]
let {footerRefreshingText, footerFailureText, footerNoMoreDataText, footerEmptyDataText} = this.props
switch (this.props.refreshState) {
case RefreshState.Idle:
footer = (<View style={footerContainerStyle} />)
break
case RefreshState.Failure: {
footer = (
<TouchableOpacity
style={footerContainerStyle}
onPress={() => {
this.props.onFooterRefresh && this.props.onFooterRefresh(RefreshState.FooterRefreshing)
}}
>
<Text style={footerTextStyle}>{footerFailureText}</Text>
</TouchableOpacity>
)
break
}
case RefreshState.EmptyData: {
footer = (
<TouchableOpacity
style={footerContainerStyle}
onPress={() => {
this.props.onFooterRefresh && this.props.onFooterRefresh(RefreshState.FooterRefreshing)
}}
>
<Text style={footerTextStyle}>{footerEmptyDataText}</Text>
</TouchableOpacity>
)
break
}
case RefreshState.FooterRefreshing: {
footer = (
<View style={footerContainerStyle} >
<ActivityIndicator size="small" color="#888888" />
<Text style={[footerTextStyle, {marginLeft: 7}]}>{footerRefreshingText}</Text>
</View>
)
break
}
case RefreshState.NoMoreData: {
footer = (
<View style={footerContainerStyle} >
<Text style={footerTextStyle}>{footerNoMoreDataText}</Text>
</View>
)
break
}
}
return footer
}
}
const styles = StyleSheet.create({
footerContainer: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
padding: 10,
height: 44,
},
footerText: {
fontSize: 14,
color: '#555555'
}
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
编写子布局
/**
* 列表的Item
*/
import React, {PureComponent} from 'react'
import {View, Text, StyleSheet, TouchableOpacity, Image, PixelRatio} from 'react-native'
const color = {
theme: '#06C1AE',
border: '#e0e0e0',
background: '#f3f3f3'
}
export default class ItemCell extends PureComponent {
render() {
let {info} = this.props
let imageUrl = info.imageUrl.replace('w.h', '160.0').replace('http','https');
return (
<TouchableOpacity style={styles.container} onPress={() => this.props.onPress()}>
<Image source={{ uri: imageUrl }} style={styles.icon} />
<View style={styles.rightContainer}>
<Text>{info.title}</Text>
<View>
</View>
<Text style={styles.p} numberOfLines={0} >{info.subtitle}</Text>
<View style={{ flex: 1, justifyContent: 'flex-end' }}>
<Text style={styles.price}>{info.price}元</Text>
</View>
</View>
</TouchableOpacity>
)
}
}
const styles = StyleSheet.create({
container: {
flex:1,
flexDirection: 'row',
padding: 10,
borderBottomWidth: 1,
borderColor: '#e9e9e9',
backgroundColor: 'white',
},
icon: {
width: 80,
height: 80,
borderRadius: 5,
},
rightContainer: {
flex: 1,
paddingLeft: 20,
paddingRight: 10,
},
price: {
color: color.theme
},
h1: {
fontSize: 15,
fontWeight: 'bold',
color: '#222222',
},
p: {
fontSize: 13,
color: '#777777',
marginTop: 8,
},
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
编写主界面
为了方便我们直接使用本地的数据,主界面的代码如下:
/**
* 加载本地测试数据
*/
import React, {Component} from 'react'
import {View, StyleSheet, Platform, Dimensions} from 'react-native'
import RefreshListView, {RefreshState} from '../src/view/RefreshListView'
import Cell from '../src/ItemCell'
import data from '../src/config/data'
const {width} = Dimensions.get('window');
export default class FlatListLocalView extends Component {
state: {
dataList: Array<any>,
refreshState: number,
}
constructor(props) {
super(props)
this.state = {
dataList: [],
refreshState: RefreshState.Idle,
}
}
componentDidMount() {
this.onHeaderRefresh()
}
onHeaderRefresh = () => {
this.setState({refreshState: RefreshState.HeaderRefreshing})
// 模拟网络请求
setTimeout(() => {
// 模拟网络加载失败的情况
if (Math.random() < 0.2) {
this.setState({refreshState: RefreshState.Failure})
return
}
//获取测试数据
let dataList = this.getTestList(true)
this.setState({
dataList: dataList,
refreshState: dataList.length < 1 ? RefreshState.EmptyData : RefreshState.Idle,
})
}, 2000)
}
onFooterRefresh = () => {
this.setState({refreshState: RefreshState.FooterRefreshing})
// 模拟网络请求
setTimeout(() => {
// 模拟网络加载失败的情况
if (Math.random() < 0.2) {
this.setState({refreshState: RefreshState.Failure})
return
}
//获取测试数据
let dataList = this.getTestList(false)
this.setState({
dataList: dataList,
refreshState: dataList.length > 50 ? RefreshState.NoMoreData : RefreshState.Idle,
})
}, 2000)
}
// 获取测试数据
getTestList(isReload: boolean): Array<Object> {
// fetch(api.recommend)
// .then((response) => response.json())
// .then((json) => {
// console.log(JSON.stringify(json));
//
// let newList = json.data.map((info) => {
// return {
// id: info.id,
// imageUrl: info.squareimgurl,
// title: info.mname,
// subtitle: `[${info.range}]${info.title}`,
// price: info.price
// }
// })
// return isReload ? newList: [...this.state.dataList, ...newList]
// })
let newList = data.map((data) => {
return {
imageUrl: data.squareimgurl,
title: data.mname,
subtitle: `[${data.range}]${data.title}`,
price: data.price,
}
})
return isReload ? (Math.random() < 0.2 ? [] : newList) : [...this.state.dataList, ...newList]
}
keyExtractor = (item: any, index: number) => {
return index
}
renderCell = (info: Object) => {
return <Cell info={info.item}/>
}
render() {
return (
<View style={styles.container}>
<RefreshListView
data={this.state.dataList}
keyExtractor={this.keyExtractor}
renderItem={this.renderCell}
refreshState={this.state.refreshState}
onHeaderRefresh={this.onHeaderRefresh}
onFooterRefresh={this.onFooterRefresh}
// 可选
footerRefreshingText='玩命加载中...'
footerFailureText='我擦嘞,居然加载失败了...'
footerNoMoreDataText='我是有底线的...'
footerEmptyDataText='好像什么东西都没有...'
/>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
width: width,
marginTop: Platform.OS == 'ios' ? 20 : 0,
},
title: {
fontSize: 18,
height: 84,
textAlign: 'center'
}
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
数据的话,大家可以参考下面的格式:
export default
[
{
"rating": 4.6,
"range": "150店通用",
"mname": "吉野家",
"title": "卤肉饭+乌龙茶(小)1份",
"price": 10,
"squareimgurl": "http://p0.meituan.net/w.h/deal/5911c9d9235036c6fc11fcb1dbcb5bce27954.jpg@87_0_266_266a%7C267h_267w_2e_100Q",
},
{
"rating": 4.4,
"range": "北京等",
"mname": "真功夫",
"title": "冬(香)菇鸡腿肉饭\t+\t卤蛋1份",
"price": 15,
"squareimgurl": "http://p1.meituan.net/w.h/deal/15c8885d14f18774938a88752f08bb1e49194.jpg@118_0_466_466a%7C267h_267w_2e_90Q",
},
{
"rating": 4.2,
"range": "46店通用",
"mname": "京八珍",
"title": "50元代金券1张,可叠加",
"price": 65,
"squareimgurl": "http://p0.meituan.net/w.h/deal/d57d5f0644256a3013469edfc1406e8022163.jpg",
},
{
"rating": 4.2,
"range": "2店通用",
"mname": "麻里麻里",
"title": "2人餐,提供免费WiFi",
"price": 78,
"squareimgurl": "http://p0.meituan.net/w.h/deal/f436e044254128059f055f2275eadbb837054.jpg",
},
{
"rating": 4.4,
"range": "2店通用",
"mname": "东来顺饭庄",
"title": "4-5人套餐,百年老字号",
"price": 168,
"squareimgurl": "http://p0.meituan.net/w.h/deal/416d01cbc4b8a2871b3c260615b5998088199.jpg",
},
{
"rating": 4.2,
"range": "12店通用",
"mname": "果麦de鲜饮创作",
"title": "饮品3选1,提供免费WiFi",
"price": 7.99,
"squareimgurl": "http://p1.meituan.net/w.h/deal/d72d34a7038e8cca2d09406ec7dc5c83133480.jpg@0_297_1332_1332a%7C267h_267w_2e_90Q",
},
{
"rating": 4,
"range": "4店通用",
"mname": "夹拣成厨麻辣烫",
"title": "50元代金券1张,可叠加",
"price": 39,
"squareimgurl": "http://p1.meituan.net/w.h/deal/712801d4f3562706f596cd366376889f25073.jpg@71_0_444_444a%7C267h_267w_2e_90Q",
},
{
"rating": 4.6,
"range": "150店通用",
"mname": "吉野家",
"title": "卤肉饭+乌龙茶(小)1份",
"price": 10,
"squareimgurl": "http://p0.meituan.net/w.h/deal/5911c9d9235036c6fc11fcb1dbcb5bce27954.jpg@87_0_266_266a%7C267h_267w_2e_100Q",
},
{
"rating": 4.4,
"range": "北京等",
"mname": "真功夫",
"title": "冬(香)菇鸡腿肉饭\t+\t卤蛋1份",
"price": 15,
"squareimgurl": "http://p1.meituan.net/w.h/deal/15c8885d14f18774938a88752f08bb1e49194.jpg@118_0_466_466a%7C267h_267w_2e_90Q",
},
{
"rating": 4.2,
"range": "46店通用",
"mname": "京八珍",
"title": "50元代金券1张,可叠加",
"squareimgurl": "http://p0.meituan.net/w.h/deal/d57d5f0644256a3013469edfc1406e8022163.jpg",
},
{
"rating": 4.2,
"range": "2店通用",
"mname": "麻里麻里",
"title": "2人餐,提供免费WiFi",
"price": 78,
"squareimgurl": "http://p0.meituan.net/w.h/deal/f436e044254128059f055f2275eadbb837054.jpg",
},
{
"rating": 4.4,
"range": "2店通用",
"mname": "东来顺饭庄",
"title": "4-5人套餐,百年老字号",
"price": 168,
"squareimgurl": "http://p0.meituan.net/w.h/deal/416d01cbc4b8a2871b3c260615b5998088199.jpg",
},
{
"rating": 4.2,
"range": "12店通用",
"mname": "果麦de鲜饮创作",
"title": "饮品3选1,提供免费WiFi",
"price": 7.99,
"squareimgurl": "http://p1.meituan.net/w.h/deal/d72d34a7038e8cca2d09406ec7dc5c83133480.jpg@0_297_1332_1332a%7C267h_267w_2e_90Q",
},
{
"rating": 4,
"range": "4店通用",
"mname": "夹拣成厨麻辣烫",
"title": "50元代金券1张,可叠加",
"price": 39,
"squareimgurl": "http://p1.meituan.net/w.h/deal/712801d4f3562706f596cd366376889f25073.jpg@71_0_444_444a%7C267h_267w_2e_90Q",
},
]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
网络请求
上面是使用的是本地的Json数据,那么如果加载网络的数据又该怎么做呢?下面给一个简单的参考实例。
首先,我们在componentDidMount()函数中发起请求,代码如下:
componentDidMount() {
this.getTestList()
}
- 1
- 2
- 3
然后我们使用fetch来真正的发起请求,该段代码如下:
getTestList() {
fetch(api.recommend)
.then((response) => response.json())
.then((json) => {
//打印返回数据
console.log("response"+JSON.stringify(json));
let list = json.data.map((info) => {
return {
imageUrl: info.squareimgurl,
title: info.mname,
subtitle: `[${info.range}]${info.title}`,
price: info.price
}
})
this.setState({refreshState: RefreshState.Idle})
console.log("newList"+list);
this.setState({
dataList: list,
refreshState: list.length <1 ? RefreshState.EmptyData : RefreshState.Idle,
})
}).catch((error) => {
this.setState({refreshState: RefreshState.Failure})
})
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
根据异步返回的结果,我们来改变RefreshListView控件的不同显示状态,RefreshListView的显示状态又:
Idle: 0, //默认状态
HeaderRefreshing: 1, //下拉刷新
FooterRefreshing: 2, //上拉加载更多
NoMoreData: 3, //暂无数据
Failure: 4, //加载失败,如网络原因
EmptyData: 5, //空页面
- 1
- 2
- 3
- 4
- 5
- 6
当然,为了方便查看数据的返回情况,我们一般会使用调试来观察数据,进而进行数据的相关处理。如下图: