Tips:
下面gif图2.7MB左右,网络很差可能加载有问题(没法打开请点击此图片连接单独查看) javascript
上拉加载更多的细节:html
优化项vue
数据结构来源7七月老师的(风袖API文档)java
{
"total":1,
"count":10,
"page":0,
"total_page":1,
"items":[
{
"id":8,
"title":"ins复古翠绿NoteBook",
"subtitle":"林白默默的掏出小本本,将她说的话一次不漏的记了下来。",
"img":"",
"for_theme_img":"",
"price":"29.99",
"discount_price":"27.8",
"description":null,
"tags":"林白推荐",
"sketch_spec_id":"1",
"max_purchase_quantity":null,
"min_purchase_quantity":null
}
]
}
复制代码
// /model/Products.js
class Products {
static store = [
{
id: 'P001',
title: '人间值得',
subtitle: '愿你遍历山河,仍觉人间值得!',
img: '/images/人间值得.png',
price: "49.90",
discount_price: "46.30",
labels: ['人间值得', '恒子奶奶'],
for_theme_img: "",
},
// .....本文这里给出一条数据,其他的省略
]
constructor() {
this.total = Products.store.length;
}
async getPorductList({ count = 5, page = 1 }) {
this.count = count;
this.page = page;
this.total_page = Math.ceil(this.total / this.count);
const start = (this.page - 1) * this.count;
const end = this.page * this.count;
this.items = Products.store.slice(start, end);
return new Promise((resolve) => {
resolve(this._getDataTemplate())
})
}
_getDataTemplate() {
return {
total: this.total,
count: this.count,
total_page: this.total_page,
page: this.page,
items: this.items
}
}
}
复制代码
经过构造一个Products类,模拟数据库以及对数据库的请求。git
store
表明数据库中的数据,_getDataTemplate
对数据格式进行组装,模拟后端对数据的处理,getPorductList
方法模拟请求后端数据,每次请求默认5条数据,能够配置请求数据条数与请求页数,最终将数据进行包装后返回一个promise。一个项目的loading风格是统一的,这里选择了易用性而舍弃了灵活性。github
<view class="loading-container" wx:if="{{show}}">
<view class="loading" wx:if="{{loading}}">
<image class="loading-img" src="/images/loading.gif"></image>
<text class="loading-text">加载中</text>
</view>
<view class="done" wx:else>
我也是有底线的~
</view>
</view>
复制代码
经过设置show
属性来显示或隐藏loading组件,经过设置loading
属性来选择显示loading的状态ajax
data: {
loadingStatus: true, // loading状态(加载中/无数据)的控制
loadingShow: false, // loading组件的显示控制
products: [], // 展现的数据
productModel: null, // Products类建立的对象模型
currentPage: 1, // 当请求页的设置
pageCount: 5 // 每页请求数据的数量
},
复制代码
async onLoad (options) {
const productModel = new Products();
const products = await productModel.getPorductList({
count: this.data.pageCount,
page: this.data.currentPage
})
this.setData({
productModel,
products: products,
});
this.renderWaterFlow();
},
renderWaterFlow() {
wx.lin.renderWaterFlow(this.data.products.items, false, () => {
this.setData({
loadingShow: false,
})
})
},
复制代码
进入页面在没有触发触底事件时,应当加载一组数据进行正常的显示。因此选择在onLoad
生命周期中进行。数据库
这里建立了Products
类的实例productModel
方便后续向后端发送请求获取数据。紧接着调用该实例的getPorductList
方法,并传入请求页与每页显示数据条数获取第一组数据,并将其更新到data中。json
最后调用lin-ui
提供的瀑布流组件进行数据的渲染。小程序
onReachBottom: function () {
console.log('触底')
if(!this.data.loadingShow) {
console.log('请求')
if (this.data.currentPage >= this.data.productModel.total_page) {
this.setData({
loadingShow: true,
loadingStatus: false
})
} else {
this.setData({
loadingShow: true,
currentPage: this.data.currentPage + 1,
})
setTimeout(() => {
this.getPorductList()
}, 3000)
}
}
},
async getPorductList() {
const products = await this.data.productModel.getPorductList({
count: this.data.pageCount,
page: this.data.currentPage
})
this.setData({
products,
})
this.renderWaterFlow();
},
复制代码
onReachBottom
是小程序提供的触底事件处理方法,咱们能够将触底后须要作的操做放在此函数中运行。
在这个函数中先忽略最外层的if
语句,剩余代码判断了当前展现的数据是否是最后一页的数据:
没有更多数据
提示的相关展现。getPorductList
方法里对(模拟的)后端进行了请求并作了数据设置,以后调用renderWaterFlow
进行瀑布流的展现,在lin-ui
瀑布流函数的回调中,能够设置将loadingShow
为false
隐藏loading组件。
连续请求的优化
上面提到先忽略onReachBottom
最外层的if语句,这里来看看这个if语句解决了什么问题,上面代码中能够看到有两个console
打印语句,一个是触底
一个是请求
,当网络稍微差的时候,咱们能够在没有接收到请求数据的时候触发屡次触底事件,这是不合理的,因此加了这个if语句,判断是否在loading了,若是在loading,则代表正在请求数据,就不该该再发送请求,不然再继续进行请求的逻辑。
上面的功能已经完成,但还能够作不少优化,好比最起码在onLoad
和onReachBottom
写不少代码看起来让人很不舒服。 但更重要的并非这个问题,而是咱们这个触底请求数据可能要在多个页面中用到,如何只写一份代码就能让不一样的页面使用这个功能就显得很重要了。
在接触这个做业时对说起的封装本身感受并无什么地方值得封装,由于事实上代码量并非不少,对量不是不少的代码不能为了封装而封装吧,想了想它的需求,才明白应该提取公用的部分,将其封装起来。
这里的方式是使用behaviors
,behavior
就相似vue
中的mixin
(本身没写太小程序,看了文档后感受这两种东西做用很是类似,将二者作类比能够更方便本身对它的理解)
behavior
不能在Page
中使用Page
有本身的用处,Component
不能替代它,如触底函数在Component中是没有的behavior
与Component
的生命周期函数不一样于Page
虽然只列举了这几个问题,可能对于开发太小程序的人还不是坑,可是对本身来讲就算坑了,踩坑和解决也花了很多功夫。
behavior的封装
// /behaviors/loadmore.js
import { Products } from '../model/ProductsTest.js';
module.exports = Behavior({
behaviors: [],
data: {
loadingStatus: true,
loadingShow: false,
products: [],
productModel: null,
currentPage: 1,
pageCount: 5
},
async attached() {
this.initData()
},
methods: {
async initData() {
const productModel = new Products();
const products = await productModel.getPorductList({
count: this.data.pageCount,
page: this.data.currentPage
})
this.setData({
productModel,
products: products,
});
this.renderWaterFlow();
},
renderWaterFlow() {
wx.lin.renderWaterFlow(this.data.products.items, false, () => {
this.setData({
loadingShow: false,
})
})
},
handleReachBottom() {
if (!this.data.loadingShow) {
if (this.data.currentPage >= this.data.productModel.total_page) {
this.setData({
loadingShow: true,
loadingStatus: false
})
} else {
this.setData({
loadingShow: true,
currentPage: this.data.currentPage + 1,
})
setTimeout(() => {
this.getPorductList()
}, 3000)
}
}
},
async getPorductList() {
const products = await this.data.productModel.getPorductList({
count: this.data.pageCount,
page: this.data.currentPage
})
this.setData({
products,
})
this.renderWaterFlow();
}
},
})
复制代码
这里的封装其实就是将以前页面中的函数进行移植,首先将原来的数据能够彻底剪切过来,以前页面的renderWaterFlow
和getPorductList
方法复制到behavior
的methods
字段中,onLoad中的代码能够彻底提取出来写一个initData()
方法,onReachBottom
中的代码提取出来写成一个handleReachBottom()
方法,将这两个方法也复制到behavior
中的methods
字段。到这里代码的移植工做就作完大部分了。
而后看看获取第一组数据的执行时机,在Page
中的时候是放在onLoad
方法中的,如今应该放到attached
方法中,attached
是behavior
的一个生命周期方法,Component
中也有这个方法。
而后比较重要的一件事就出现了,以前咱们的代码都放到了behavior
中,可是在Page中没法使用behavior
,若是直接将Page变成Component
,这将致使咱们没法监听触底事件,因此只能建立一个Compoent
,将Page中的组件复制过去。而后更改页面js的逻辑,传播触底事件
<f-loadmore reachBottom="{{reachBottom}}"></f-loadmore>
复制代码
// /views/waterflow/waterflow.js
data: {
reachBottom: false
},
onReachBottom: function () {
this.setData({
reachBottom: true
})
},
// /components/loadmore/index.js
properties: {
reachBottom: Boolean
},
observers: {
'reachBottom': function(val) {
console.log(val)
if(val) {
this.handleReachBottom();
}
}
},
复制代码
说来也巧,微信中的数据的更新与Vue不一样,当属性reachBottom
更改成true
以后,再次触底触发reachBottom
从新setData,仍然设置为true
,在组件监听reachBottom
属性变化时仍然可以监听到。
因此第一次设置reachBottom
为false,虽然会触发组件中事件的监听,但因为触底时候会设置reachBottom
为true
,咱们就能够将此次false
过滤掉,若是触发过来的都是true
,咱们就认为触发了触底事件,而后执行handleReachBottom
。
这里能够直接在Component
中执行引入过来的behavior
,引入过来的behavior
一旦被注册到当前组件,其中的各配置将都会与组件的配置项进行合并,因此能够直接使用。
当前的作法只是一个简单的例子,有时候咱们会不少地方须要调用不一样的接口,但以上例子中的作法并不支持,你能够根据本身须要,将这个封装作得更加灵活,以达到适应本身项目的目的。