小程序分頁(yè)實(shí)踐之編寫(xiě)可復(fù)用分頁(yè)組件
項(xiàng)目中遇到 tab切換列表,每個(gè)tab都需要分頁(yè)的需求,分頁(yè)流程具有相似性,于是想將分頁(yè)封裝為組件,方便應(yīng)用。
組件的應(yīng)用已寫(xiě)成一個(gè)小demo,效果如下圖所示(數(shù)據(jù)用mock模擬):

源碼可以查看:wxapp-pagination
項(xiàng)目需求
具體項(xiàng)目需求:
- 查看自己相關(guān)的會(huì)議(頁(yè)面命名為 meetings)
- tab切換,分為:
- “我的會(huì)議”(我參加的會(huì)議,后面會(huì)以 "join" 為 key區(qū)分)
- “我的預(yù)約”(我預(yù)約的會(huì)議,后面會(huì)以 "book" 為 key區(qū)分)
- 一次加載10條(size=10),拉到底部后,加載下一頁(yè)(page = page +1)
當(dāng)然,作為前端,要考慮性能方面的需求:
- 首次只加載默認(rèn)tab頁(yè)的首頁(yè),其他tab等到點(diǎn)擊到對(duì)應(yīng)tab才開(kāi)始加載。
- 回到已加載過(guò)的tab頁(yè),保留原數(shù)據(jù)不重新加載。
所以原型圖大概就長(zhǎng)這樣:

邏輯實(shí)現(xiàn)
與分頁(yè)邏輯相關(guān)的項(xiàng)目結(jié)構(gòu)如下:
├── components │ ├── meeting-item # 列表item │ └── pagination # 分頁(yè)組件 ├── model │ └── user.js # 我的相關(guān) model └── pages │ └── user # 我的相關(guān)頁(yè)面 │ ├── meetings # 我的會(huì)議(就是tab要分頁(yè)的頁(yè)面啦) │ └── ... │ └── vant-weapp
還是用圖理一下他們之間的關(guān)系吧:

在組件內(nèi)監(jiān)聽(tīng)觸發(fā)分頁(yè)事件
觸發(fā)分頁(yè)的事件是滾動(dòng)到頁(yè)面的底部,小程序中,觸發(fā)該事件是Page頁(yè)面的onReachBottom事件,但是這個(gè)事件只能在Page中觸發(fā),所以要將這個(gè)觸發(fā)時(shí)機(jī)傳遞給pagination組件。
解決思路是:組件 pagination 內(nèi),設(shè)置key屬性,每當(dāng)onReachBottom事件觸發(fā)之后,設(shè)置組件屬性 key 為一個(gè)隨機(jī)字符串,當(dāng)組件 pagination 監(jiān)聽(tīng)到key的變化的時(shí)候,做出分頁(yè)操作。
// components/pagination/index.js
Component({
properties: {
key: {
type: String,
observer: '_loadMoreData' // _loadMoreData 為分頁(yè)操作
}
}
})
<!-- pages/user/meetings/index.wxml -->
<tabs active="{{currentTab}}" bind:change="onChange">
<tab title="我的會(huì)議" data-key="{{type['JOIN']}}">
<view class="meeting-list">
<pagination
name="JOIN"
key="{{joinKey}}"
>
</pagination>
</view>
</tab>
<tab title="我的預(yù)約">
<view class="meeting-list">
<pagination
name="BOOK"
key="{{bookKey}}"
>
</pagination>
</view>
</tab>
</tabs>
Page({
onReachBottom(){
const key = scene[+this.data.currentTab].key // 對(duì)應(yīng)tab對(duì)應(yīng)key
this.setData({
[key]: random(16)
})
}
})
分頁(yè)組件的實(shí)現(xiàn)邏輯
觸發(fā)到達(dá)底部之后,需要加載數(shù)據(jù)。但再加載之前,先滿足加載的條件:
- 上一頁(yè)還未加載完成(loading = true),不重復(fù)加載
- 當(dāng)前頁(yè)面全部加載完(ended = true),不繼續(xù)加載
具體加載流程為:
- page:觸發(fā) onReachBottom 事件,修改 pagination組件 key 值
- component: key值監(jiān)聽(tīng)到變化,觸發(fā)加載事件 _loadMoreData
- component: _loadMoreData 中判斷滿足條件后,觸發(fā)加載列表函數(shù) this.triggerEvent('getList'),并傳入頁(yè)面參數(shù)page 和 size。
- page:向model層獲取數(shù)據(jù)。
- page:獲取數(shù)據(jù)后,傳遞給 pagination組件list和total 值。
- component:list 監(jiān)聽(tīng)到變化,判斷是否加載完成。
component
// components/pagination/index.js
Component({
properties: {
name: String,
key: {
type: String,
observer: '_loadMoreData' // _loadMoreData 為分頁(yè)操作
},
size: { // 每次加載條目數(shù)
type: Number,
value: 10
},
total: Number, // 頁(yè)面總數(shù)
list: { // 已加載條目
type: Array,
observer: '_endState' // 每次加載完新數(shù)據(jù),判斷數(shù)據(jù)是否全部加載完成
}
},
data: {
page: 0, // 當(dāng)前第幾頁(yè)
loading: false, // 是否正在加載
ended: false // 數(shù)據(jù)是否已全部加載完成
},
methods: {
_loadMoreData(){
const { loading, ended, size } = this.data
if (loading) return // 上一頁(yè)還未加載完成,不加載
if (ended) return // 當(dāng)前頁(yè)面全部加載完,不加載
const page = this.data.page + 1
this.setData({
loading: true, // 開(kāi)始加載新頁(yè)面loading設(shè)置為true
page
})
// 觸發(fā)加載下一頁(yè),并傳入?yún)?shù)
this.triggerEvent('getList', {
page,
size
})
},
_endState(val, oldVal) {
const { total, list } = this.properties
let ended = false
// 判斷數(shù)據(jù)是否全部加載完成
if (list.length >= total) {
ended = true
}
this.setData({
loading: false, // 當(dāng)前頁(yè)面加載完成,loading設(shè)置為false
ended
})
}
}
})
page
<!-- pages/user/meetings/index.wxml -->
<pagination
name="BOOK"
key="{{bookKey}}"
bind:getList="getBookMeetings"
list="{{bookMeetings}}"
total="{{bookTotal}}"
>
</pagination>
循環(huán)列表item
pagination組件獲取了可循環(huán)的列表,初始想法是循環(huán)slot,但是在slot中卻獲取不到 item 對(duì)象:
<view wx:for="{{list}}" wx:for-item="item" wx:key="index">
<slot></slot>
</view>
解決的辦法是將每個(gè)列表項(xiàng)封裝為組件,循環(huán)抽象節(jié)點(diǎn),這樣對(duì)其他頁(yè)面的分頁(yè)具有可拓展性。
componentGenerics 字段中聲明:
// components/pagination/index.json
{
"componentGenerics": {
"selectable": true
},
// ...
}
使用抽象節(jié)點(diǎn):
<!-- components/pagination/index.wxml -->
<view wx:for="{{list}}" wx:for-item="item" wx:key="index">
<selectable item="{{item}}"></selectable>
</view>
指定“selectable”具體是哪個(gè)組件:
<!-- pages/user/meetings/index.wxml --> <pagination generic:selectable="meeting-item" // ... 其他屬性 > </pagination>
對(duì)應(yīng) json 文件定義對(duì)應(yīng) usingComponents :
// pages/user/meetings/index.json
{
"usingComponents": {
"pagination":"/components/pagination/index",
"meeting-item":"/components/meeting-item/index"
}
}
meeting-item 組件接收一個(gè)屬性 item,這樣在 meeting-item 組件中,就可以獲取到循環(huán)列表的item值,并正常繪制頁(yè)面。
實(shí)現(xiàn)切換懶加載
給pagination添加initImmediately屬性,當(dāng)initImmediately為true時(shí),首次加載頁(yè)面,并用變量 initState標(biāo)記是否已經(jīng)初始化頁(yè)面過(guò)。
// components/pagination/index.js
Component({
properties: {
initImmediately: {
type: Boolean,
value: true,
observer: function(val){
if (val && !this.data.initState) {
this.initData()
}
}
},
// ...
},
data: {
initState: false, // 是否已經(jīng)加載過(guò)
// ...
},
lifetimes: {
attached: function () {
if (this.data.initImmediately){
this.initData()
}
},
},
methods: {
initData(){
console.info(`${this.data.name}:start init data`)
this._loadMoreData()
this.setData({
initState: true
})
},
// ...
_endState(val, oldVal) {
if (!this.data.initState) return
// ...
},
}
})
當(dāng)currentTab為當(dāng)前類型的時(shí)候,initImmediately 設(shè)置為 true。
<!-- pages/user/meetings/index.wxml -->
<pagination
name="JOIN"
init-immediately="{{currentTab==type['JOIN']}}"
// ...
>
</pagination>
<pagination
name="BOOK"
init-immediately="{{currentTab==type['BOOK']}}"
// ...
>
</pagination>
組件的復(fù)用
完成了以上組件,在對(duì)其他分頁(yè)的頁(yè)面,可以直接復(fù)用。比如,實(shí)現(xiàn)一個(gè)獲取全部用戶列表的分頁(yè),只需要新增一個(gè)user-item的組件,像這樣調(diào)用:
<pagination
name="USER"
key="{{key}}"
bind:getList="getUserList"
list="{{userList}}"
total="{{userTotal}}"
generic:selectable="user-item"
>
</pagination>
具體應(yīng)用可以查看我寫(xiě)的小demo:wxapp-pagination。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 微信小程序?qū)崿F(xiàn)列表分頁(yè)功能
- 微信小程序?qū)崿F(xiàn)分頁(yè)查詢?cè)斀?/a>
- 小程序?qū)崿F(xiàn)分頁(yè)效果
- 微信小程序?qū)崿F(xiàn)分頁(yè)加載效果
- 微信小程序之搜索分頁(yè)功能的實(shí)現(xiàn)代碼
- 微信小程序云開(kāi)發(fā)實(shí)現(xiàn)數(shù)據(jù)添加、查詢和分頁(yè)
- 微信小程序分頁(yè)加載的實(shí)例代碼
- 微信小程序?qū)崙?zhàn)之上拉(分頁(yè)加載)效果(2)
- 微信小程序模板之分頁(yè)滑動(dòng)欄
- 小程序?qū)崿F(xiàn)簡(jiǎn)單分頁(yè)組件
相關(guān)文章
JavaScript通過(guò)正則表達(dá)式實(shí)現(xiàn)表單驗(yàn)證電話號(hào)碼
JavaScript判斷一個(gè)輸入量是否為電話號(hào)碼,通過(guò)正則表達(dá)式實(shí)現(xiàn),需要的朋友可以參考下2014-03-03
javascript實(shí)現(xiàn)簡(jiǎn)單的Map示例介紹
本文為大家介紹下使用javascript實(shí)現(xiàn)簡(jiǎn)單的Map,可以對(duì)map進(jìn)行獲取、判斷、刪除、增加等等,感興趣的朋友可以了解下2013-12-12
JavaScript實(shí)現(xiàn)時(shí)間范圍效果
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)時(shí)間范圍效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05
js使用removeChild方法動(dòng)態(tài)刪除div元素
本節(jié)為大家介紹了js使用removeChild方法動(dòng)態(tài)刪除div元素,需要的朋友可以參考下2014-08-08
js 實(shí)現(xiàn)數(shù)值的千分位及保存小數(shù)方法(推薦)
下面小編就為大家?guī)?lái)一篇js 實(shí)現(xiàn)數(shù)值的千分位及保存小數(shù)方法(推薦)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08
JavaScript中call和apply方法的區(qū)別實(shí)例分析
這篇文章主要介紹了JavaScript中call和apply方法的區(qū)別,結(jié)合實(shí)例形式分析call和apply方法的功能、原理及相關(guān)使用操作區(qū)別,需要的朋友可以參考下2018-08-08
JavaScript實(shí)現(xiàn)復(fù)選框全選或全取消操作
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)復(fù)選框全選或全取消操作,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09

