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
一旦被註冊到當前組件,其中的各配置將都會與組件的配置項進行合併,因此能夠直接使用。
當前的作法只是一個簡單的例子,有時候咱們會不少地方須要調用不一樣的接口,但以上例子中的作法並不支持,你能夠根據本身須要,將這個封裝作得更加靈活,以達到適應本身項目的目的。