前言

若文章有误,欢迎读者留言反馈

💻Installation

1
git clone https://github.com/coding327/mymovies.git

上拉加载更多数据

分类页面的这个数据是有total的,但是目前我们只请求了12条,有的已经超过了12条,我们本来就是要看电影的只加载12条显然不合适,但是一下子请求如500条也不合适,需要考虑用户体验,加载太慢数据一直不出来,占用网络带宽,先只加载12条,用户手指下滑或上拉加载更多电影,当电影加载完,显示没有更多了

上拉加载:小程序框架/框架接口/页面Page中是有一个这样的事件监听的函数的onReachBottom页面上拉触底事件的处理函数

回到list.js文件中

1
2
3
4
5
6
7
8
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
console.log(123) // 这里我们可以测试一下上拉功能
// 发请求获取数据由于起始索引是从0开始,但是下次加载就不能从0开始了,说明start不能写死,需要去数据仓库中定义数据,count是返回多少条数据,也可以不用写死
this.loadListData() // 调用加载电影数据的方法
},

上面这样调用加载数据方法还不行,需要改造loadListData方法

发请求获取数据由于起始索引是从0开始,但是下次加载就不能从0开始了,说明start不能写死,需要去数据仓库中定义数据,count是返回多少条数据,也可以不用写死

1
2
3
4
5
6
7
8
9
/**
* 页面的初始数据
*/
data: {
method: '', // 调api的方法名
films: {}, // 存放电影信息,由于有个分类名,这里设计为对象格式的数据
start: 0, // 起始索引
count: 12 // 返回多少条数据
},

更改完,初始加载数据的那个方法也可以更改了
同时也出现一个问题—->当每次有人上拉时调请求数据方法,起始索引值都要比之前多12
解决方案:我们可以每次请求完数据,在then里面更改start值为start+count

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 定义加载数据方法
loadListData() {
// 注意method是个变量这里要用中括号
api[this.data.method]({
start: this.data.start,
count: this.data.count
}).then(data => {
// console.log(data) // 成功拿到数据
// 定义一个films,再赋值给数据仓库中的films即可
let films = {
title: data.subject_collection.name,
list: data.subject_collection_items
}
this.setData({
// films: films
// 由于对象属性名与属性值的变量名相同可以简写为films
films,
start: this.data.start + this.data.count // 起始索引增加
})
}).catch(api.showError)
// 这个写完可以测试一下,学会使用控制台的AppData,它是可以帮我们看这个数据的,如films中的title和list
},

新的问题也出现了,它总是显示新的12条,原来的数据没了
解决方案:之前我们使用的films是将每次获取的电影films直接赋值给数据仓库中的films,很显然它就一直都是新的12条,我们只需要做电影数据的concat累加即可
其中需要注意细节,初始films中由于没有listlistundefined不能使用concat方法需要给它赋个[]空数组
我们更改的只是list

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
// 定义加载数据方法
loadListData() {
// 注意method是个变量这里要用中括号
api[this.data.method]({
start: this.data.start,
count: this.data.count
}).then(data => {
// console.log(data) // 成功拿到数据
// 初始films中没有list,即list为undefined不能使用concat方法需要给它赋个[]空数组
let list = this.data.films.list || []
// 定义一个films,再赋值给数据仓库中的films即可
let films = {
title: data.subject_collection.name,
// list: data.subject_collection_items
list: list.concat(data.subject_collection_items) // 上拉加载时数据累加
}
this.setData({
// films: films
// 由于对象属性名与属性值的变量名相同可以简写为films
films,
start: this.data.start + this.data.count // 起始索引增加
})
}).catch(api.showError)
// 这个写完可以测试一下,学会使用控制台的AppData,它是可以帮我们看这个数据的,如films中的title和list
},

我们可以在控制台NetworkXHR中查看Name注意往上面拉一下有些在下方挡住了,会有个Name,我们可以点击,右边还会出现HeadersPreviewReponse等信息

上拉加载细节优化

优化一
模拟加载的延迟,当用户上拉时,如果说数据请求很长时间才请求到,那么用户是能明显感觉得到没有这种上拉的效果在里面,为了用户体验感的做优化处理
如何模拟?
和之前一样使用延时器发请求加载数据即可—->调那个loadListData方法

1
2
3
4
5
6
7
8
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
setTimeout(() => {
this.loadListData()
}, 3000) // 模拟加载延迟,调用加载电影数据的方法
},

怎么优化呢?
之前我们就写过加载动画(loading)的模板了,这里我们也给它加上这个,进入list.wxml文件中,最下方加上如下代码

1
2
<!-- 显示loading正在加载 -->
<include src="/templates/loading/loading" />

但是这里注意一个问题,这个loading其实一直都在,我们还需要处理什么时候loading不需要了—->数据加载完【有数据了,成功拿到数据了】
开关法—->使用一个布尔值即可,在list.js数据仓库中定义一个showLoading

1
2
3
4
5
6
7
8
9
10
/**
* 页面的初始数据
*/
data: {
method: '', // 调api的方法名
films: {}, // 存放电影信息,由于有个分类名,这里设计为对象格式的数据
start: 0, // 起始索引
count: 12, // 返回多少条数据
showLoading: false // 是否显示loading,只有上拉加载数据还未响应时需要显示loading--->所以设置为false
},

回到list.wxml中,添加判断是否显示

1
2
<!-- 显示loading正在加载 -->
<include wx:if="{{ showLoading }}" src="/templates/loading/loading" />

上拉时还需要显示loading,同时拿到数据让loading隐藏
【注意发请求拿数据,调用api是异步,能直接在下面更改布尔值?】
很显然不能,解决方案
我们的api是基于promise封装的,不管是.then还是.catch,它们最终得到的还是promise对象,我们只需要在loadListData函数中把那个promise对象给return出去即可,接着进行.then操作

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
// 定义加载数据方法
loadListData() {
// 注意method是个变量这里要用中括号
// 返回Promise对象,便于继续执行
return api[this.data.method]({
start: this.data.start,
count: this.data.count
}).then(data => {
// console.log(data) // 成功拿到数据
// 初始films中没有list,即list为undefined不能使用concat方法需要给它赋个[]空数组
let list = this.data.films.list || []
// 定义一个films,再赋值给数据仓库中的films即可
let films = {
title: data.subject_collection.name,
// list: data.subject_collection_items
list: list.concat(data.subject_collection_items) // 上拉加载时数据累加
}
this.setData({
// films: films
// 由于对象属性名与属性值的变量名相同可以简写为films
films,
start: this.data.start + this.data.count // 起始索引增加
})
}).catch(api.showError)
// 这个写完可以测试一下,学会使用控制台的AppData,它是可以帮我们看这个数据的,如films中的title和list
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
// console.log(123) // 这里我们可以测试一下上拉功能

// 上拉时先让loading显示
this.setData({
showLoading: true
})

// 发请求获取数据由于起始索引是从0开始,但是下次加载就不能从0开始了,说明start不能写死,需要去数据仓库中定义数据,count是返回多少条数据,也可以不用写死
// this.loadListData() // 调用加载电影数据的方法
setTimeout(() => {
// 这里的.then一定别忘了上面要loadListData要加上return
this.loadListData().then(() => {
// 加载完数据隐藏loading
this.setData({
showLoading: false
})
})
}, 3000) // 模拟加载延迟,调用加载电影数据的方法
},

测试阶段,点击AppData,在运行的页面上进行上拉操作,查看list页面的showLoading值的变化
false--->true--->false

优化二
如果后台没数据给你返回了,那么就没必要再发无意义的请求,依旧是在上拉加载中处理
【问题】怎么判断还有数据?
这时候total就发挥作用了,后台返回数据中一般对于list这种它都会给我们返回total这么一个字段的,通过它能告诉我们还有多少条数据可以取,同时在数据仓库中定义一个total,把响应数据里的total给它存起来

1
2
3
4
5
6
7
8
9
10
11
/**
* 页面的初始数据
*/
data: {
method: '', // 调api的方法名
films: {}, // 存放电影信息,由于有个分类名,这里设计为对象格式的数据
start: 0, // 起始索引
count: 12, // 返回多少条数据
total: 0, // 后台数据库中电影总条数
showLoading: false // 是否显示loading,只有上拉加载数据还未响应时需要显示loading--->所以设置为false
},

发完请求还需要赋值total

1
2
3
4
5
6
7
this.setData({
// films: films
// 由于对象属性名与属性值的变量名相同可以简写为films
films,
start: this.data.start + this.data.count, // 起始索引增加
total: data.total // 后台数据库中电影总条数
})

上拉只有在后台数据库中还有数据才发请求调用加载数据的方法,如何判断还有数据?
通过start < total

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
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
// console.log(123) // 这里我们可以测试一下上拉功能

// 判断是否还有更多数据,如果有就显示loading再加载数据;没有最好显示没有更多数据
if (this.data.start < this.data.total) {
// 上拉时先让loading显示
this.setData({
showLoading: true
})
// 发请求获取数据由于起始索引是从0开始,但是下次加载就不能从0开始了,说明start不能写死,需要去数据仓库中定义数据,count是返回多少条数据,也可以不用写死
// this.loadListData() // 调用加载电影数据的方法
setTimeout(() => {
// 这里的.then一定别忘了上面要loadListData要加上return
this.loadListData().then(() => {
// 加载完数据隐藏loading
this.setData({
showLoading: false
})
})
}, 3000) // 模拟加载延迟,调用加载电影数据的方法
}
},

当后台数据库中没有数据时需要显示没有更多数据,考虑到在很多页面中也会用到,所以把它封装为一个模板,单独定义没有更多的这样一个模板
templates目录下新建nomore文件夹,接着在该文件夹下新建文件nomore.wxmlnomore.wxss
nomore.wxml代码如下

1
<view class="nomore">~~没有更多了~~</view>

nomore.wxss代码如下

1
2
3
4
5
6
.nomore {
text-align: center;
font-size: 28rpx;
color: #999;
margin-bottom: 30rpx;
}

前面之前说到过了,模板的样式文件需要做全局导入(加载)样式文件
进入app.wxss文件中,完整代码如下

1
2
3
4
5
6
7
8
/* 全局导入loading */
@import "/templates/loading/loading.wxss";
/* 全局导入nomore */
@import "/templates/nomore/nomore.wxss";

page {
background-color: #efefef;
}

回到list.wxml页面中最后面加上这个nomore模板,没有更多数据时需要让其生效,需要一个布尔值来控制其显示隐藏

1
2
<!-- 显示nomore没有更多数据 -->
<include wx:if="{{ showNomore }}" src="/templates/nomore/nomore" />

同时回到list.js中,到数据仓库中添加showNomore,值默认为false

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 页面的初始数据
*/
data: {
method: '', // 调api的方法名
films: {}, // 存放电影信息,由于有个分类名,这里设计为对象格式的数据
start: 0, // 起始索引
count: 12, // 返回多少条数据
total: 0, // 后台数据库中电影总条数
showLoading: false, // 是否显示loading,只有上拉加载数据还未响应时需要显示loading--->所以设置为false
showNomore: false // 是否显示nomore
},

什么时候改为true,当没有数据的时候,也就是else,改为true

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
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
// console.log(123) // 这里我们可以测试一下上拉功能

// 判断是否还有更多数据,如果有就显示loading再加载数据;没有最好显示没有更多数据
if (this.data.start < this.data.total) { // 还有更多数据
// 上拉时先让loading显示
this.setData({
showLoading: true
})
// 发请求获取数据由于起始索引是从0开始,但是下次加载就不能从0开始了,说明start不能写死,需要去数据仓库中定义数据,count是返回多少条数据,也可以不用写死
// this.loadListData() // 调用加载电影数据的方法
setTimeout(() => {
// 这里的.then一定别忘了上面要loadListData要加上return
this.loadListData().then(() => {
// 加载完数据隐藏loading
this.setData({
showLoading: false
})
})
}, 3000) // 模拟加载延迟,调用加载电影数据的方法
} else { // 没有更多数据,显示nomore
this.setData({
showNomore: true
})
}
},

下拉刷新

和上拉加载一样也有一个监听下拉刷新的事件处理函数即onPullDownRefresh,首先需要在list.json中启用它
【注意】但是有个问题它的效果不是很明显,需要给它加样式,backgroundColor是下拉的那个背景色,backgroundTextStyle是三个点点点的样式,只有darklight可选

1
2
3
4
5
6
{
"usingComponents": {},
"enablePullDownRefresh": true,
"backgroundColor": "#efefef",
"backgroundTextStyle": "dark"
}

上面这些设置只是效果上的,忽悠人的,下拉刷新我们还需要使用那个函数重新加载新的数据,回到list.js文件中,使用onPullDownRefresh函数
怎么重新加载新的数据?
把值都恢复默认就行了

1
2
3
4
5
6
7
8
9
10
11
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
// 重新加载数据即把值都恢复默认就行了
this.setData({
start: 0,
films: {}
})
this.loadListData()
},

测试,我们可以在调试器中的Network中观察这个start是不是变成0了

list.js完整代码如下

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
// pages/list/list.js
// 导入api模块
const api = require('../../api/api.js')
Page({

/**
* 页面的初始数据
*/
data: {
method: '', // 调api的方法名
films: {}, // 存放电影信息,由于有个分类名,这里设计为对象格式的数据
start: 0, // 起始索引
count: 12, // 返回多少条数据
total: 0, // 后台数据库中电影总条数
showLoading: false, // 是否显示loading,只有上拉加载数据还未响应时需要显示loading--->所以设置为false
showNomore: false // 是否显示nomore
},

/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
// console.log(options); // 成功拿到method
// 放到数据仓库中
this.data.method = options.method
// 这里赋值为何不采用setData呢?
// this.setData是可以实现数据的响应式,只要页面用到的地方都能及时的更新,但是method的值我们只需要固定的,并且不需要在页面上展示,所以这里两个方法都可以
// this.setData({
// method: options.method
// })
this.loadListData()
},
// 定义加载数据方法
loadListData() {
// 注意method是个变量这里要用中括号
// 返回Promise对象,便于继续执行
return api[this.data.method]({
start: this.data.start,
count: this.data.count
}).then(data => {
// console.log(data) // 成功拿到数据
// 初始films中没有list,即list为undefined不能使用concat方法需要给它赋个[]空数组
let list = this.data.films.list || []
// 定义一个films,再赋值给数据仓库中的films即可
let films = {
title: data.subject_collection.name,
// list: data.subject_collection_items
list: list.concat(data.subject_collection_items) // 上拉加载时数据累加
}
this.setData({
// films: films
// 由于对象属性名与属性值的变量名相同可以简写为films
films,
start: this.data.start + this.data.count, // 起始索引增加
total: data.total // 后台数据库中电影总条数
})
}).catch(api.showError)
// 这个写完可以测试一下,学会使用控制台的AppData,它是可以帮我们看这个数据的,如films中的title和list
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {

},

/**
* 生命周期函数--监听页面显示
*/
onShow: function () {

},

/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {

},

/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {

},

/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
// 重新加载数据即把值都恢复默认就行了
this.setData({
start: 0,
films: {}
})
this.loadListData()
},

/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
// console.log(123) // 这里我们可以测试一下上拉功能

// 判断是否还有更多数据,如果有就显示loading再加载数据;没有最好显示没有更多数据
if (this.data.start < this.data.total) { // 还有更多数据
// 上拉时先让loading显示
this.setData({
showLoading: true
})
// 发请求获取数据由于起始索引是从0开始,但是下次加载就不能从0开始了,说明start不能写死,需要去数据仓库中定义数据,count是返回多少条数据,也可以不用写死
// this.loadListData() // 调用加载电影数据的方法
setTimeout(() => {
// 这里的.then一定别忘了上面要loadListData要加上return
this.loadListData().then(() => {
// 加载完数据隐藏loading
this.setData({
showLoading: false
})
})
}, 3000) // 模拟加载延迟,调用加载电影数据的方法
} else { // 没有更多数据,显示nomore
this.setData({
showNomore: true
})
}
},

/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {

}
})