小程序分頁實踐:編寫可複用分頁組件

項目中遇到 tab切換列表,每一個tab都須要分頁的需求,分頁流程具備類似性,因而想將分頁封裝爲組件,方便應用。javascript

組件的應用已寫成一個小demo,效果以下圖所示(數據用mock模擬):html

wxapp-pagination.gif

源碼能夠查看:wxapp-pagination前端

項目需求

具體項目需求:java

  • 查看本身相關的會議(頁面命名爲 meetings)
  • tab切換,分爲:
    • 「個人會議」(我參加的會議,後面會以 "join" 爲 key區分)
    • 「個人預定」(我預定的會議,後面會以 "book" 爲 key區分)
  • 一次加載10條(size=10),拉到底部後,加載下一頁(page = page +1)

固然,做爲前端,要考慮性能方面的需求git

  • 首次只加載默認tab頁的首頁,其餘tab等到點擊到對應tab纔開始加載。
  • 回到已加載過的tab頁,保留原數據不從新加載。

因此原型圖大概就長這樣:
github

image.png

邏輯實現

與分頁邏輯相關的項目結構以下:json

├── components
│		├── meeting-item	  # 列表item
│		└── pagination	          # 分頁組件
├── model
│   └── user.js				  # 個人相關 model
└── pages
│	   └── user	                  # 個人相關頁面
│       ├── meetings		          # 個人會議(就是tab要分頁的頁面啦)
│       └── ...
│
└── vant-weapp
複製代碼

仍是用圖理一下他們之間的關係吧:小程序

image.png

在組件內監聽觸發分頁事件

觸發分頁的事件是滾動到頁面的底部,小程序中,觸發該事件是Page頁面的onReachBottom事件,可是這個事件只能在Page中觸發,因此要將這個觸發時機傳遞給pagination組件。api

解決思路是:組件 pagination 內,設置key屬性,每當onReachBottom事件觸發以後,設置組件屬性 key  爲一個隨機字符串,當組件 pagination 監聽到key的變化的時候,作出分頁操做。bash

// components/pagination/index.js
Component({
  properties: {
    key: {
      type: String,
      observer: '_loadMoreData'  // _loadMoreData 爲分頁操做
    }
  }
})
複製代碼
<!-- pages/user/meetings/index.wxml -->
<tabs active="{{currentTab}}" bind:change="onChange">
    <tab title="個人會議" data-key="{{type['JOIN']}}">
      <view class="meeting-list">
          <pagination name="JOIN" key="{{joinKey}}" >
          </pagination>
      </view>
    </tab>

    <tab title="個人預定">
      <view class="meeting-list">
        <pagination name="BOOK" key="{{bookKey}}" >
        </pagination>
      </view>
    </tab>
</tabs>
複製代碼
Page({
  onReachBottom(){
    const key = scene[+this.data.currentTab].key // 對應tab對應key
    this.setData({
      [key]: random(16)
    })
  }
})
複製代碼

分頁組件的實現邏輯

觸發到達底部以後,須要加載數據。但再加載以前,先知足加載的條件:

  • 上一頁還未加載完成(loading = true),不重複加載
  • 當前頁面所有加載完(ended = true),不繼續加載

具體加載流程爲:

  1. **page:**觸發 onReachBottom 事件,修改 pagination組件 key
  2. component: key值監聽到變化,觸發加載事件_loadMoreData
  3. component:_loadMoreData中判斷知足條件後,觸發加載列表函數 this.triggerEvent('getList'),並傳入頁面參數page 和 size。
  4. **page:**向model層獲取數據。
  5. **page:**獲取數據後,傳遞給 pagination組件listtotal 值。
  6. component:list 監聽到變化,判斷是否加載完成。

component

// components/pagination/index.js
Component({
  properties: {
    name: String,
    key: {
      type: String,
      observer: '_loadMoreData'  // _loadMoreData 爲分頁操做
    },
    size: {  // 每次加載條目數
      type: Number,
      value: 10
    },
    total: Number, // 頁面總數
    list: {				 // 已加載條目
      type: Array,
      observer: '_endState'     // 每次加載完新數據,判斷數據是否所有加載完成
    }
  },

  data: {
    page: 0,				// 當前第幾頁
    loading: false, // 是否正在加載
    ended: false    // 數據是否已所有加載完成
  },
  
  methods: {
    _loadMoreData(){
      const { loading, ended, size } = this.data
      if (loading) return  // 上一頁還未加載完成,不加載
      if (ended) return    // 當前頁面所有加載完,不加載
      const page = this.data.page + 1

      this.setData({
        loading: true,  // 開始加載新頁面loading設置爲true
        page
      })
      // 觸發加載下一頁,並傳入參數
      this.triggerEvent('getList', {
        page,
        size
      })
    },
    _endState(val, oldVal) {
      const { total, list } = this.properties
      let ended = false
      // 判斷數據是否所有加載完成
      if (list.length >= total) {
        ended = true
      }
      this.setData({
        loading: false, // 當前頁面加載完成,loading設置爲false
        ended
      })
    }
  }
})
複製代碼

page

<!-- pages/user/meetings/index.wxml -->
<pagination name="BOOK" key="{{bookKey}}" bind:getList="getBookMeetings" list="{{bookMeetings}}" total="{{bookTotal}}" >
</pagination>
複製代碼

循環列表item

pagination組件獲取了可循環的列表,初始想法是循環slot,可是在slot中卻獲取不到 item 對象:

<view wx:for="{{list}}" wx:for-item="item" wx:key="index">
  <slot></slot>
</view>
複製代碼

解決的辦法是將每一個列表項封裝爲組件,循環抽象節點,這樣對其餘頁面的分頁具備可拓展性。

componentGenerics 字段中聲明:

// components/pagination/index.json
{
  "componentGenerics": {
    "selectable": true
  },
  // ...
}
複製代碼

使用抽象節點:

<!-- components/pagination/index.wxml -->
<view wx:for="{{list}}" wx:for-item="item" wx:key="index">
    <selectable item="{{item}}"></selectable>
</view>
複製代碼

指定「selectable」具體是哪一個組件:

<!-- pages/user/meetings/index.wxml -->
<pagination generic:selectable="meeting-item" // ... 其餘屬性 >
</pagination>
複製代碼

對應 json 文件定義對應 usingComponents :

// pages/user/meetings/index.json
{
  "usingComponents": {
    "pagination":"/components/pagination/index",
    "meeting-item":"/components/meeting-item/index"
  }
}
複製代碼

meeting-item 組件接收一個屬性 item,這樣在 meeting-item 組件中,就能夠獲取到循環列表的item值,並正常繪製頁面。

實現切換懶加載

給pagination添加initImmediately屬性,當initImmediately爲true時,首次加載頁面,並用變量 initState標記是否已經初始化頁面過。

// components/pagination/index.js
Component({
  properties: {
    initImmediately: {
      type: Boolean,
      value: true,
      observer: function(val){
        if (val && !this.data.initState) {
          this.initData()
        }
      }
    },
    // ...
  },
  data: {
     initState: false, // 是否已經加載過
     // ...
  },
  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 
      // ...
  	},
  }
})
複製代碼

當currentTab爲當前類型的時候,initImmediately 設置爲 true。

<!-- pages/user/meetings/index.wxml -->
<pagination name="JOIN" init-immediately="{{currentTab==type['JOIN']}}" // ... >
</pagination>

<pagination name="BOOK" init-immediately="{{currentTab==type['BOOK']}}" // ... >
</pagination>
複製代碼

組件的複用

完成了以上組件,在對其餘分頁的頁面,能夠直接複用。好比,實現一個獲取所有用戶列表的分頁,只須要新增一個user-item的組件,像這樣調用:

<pagination name="USER" key="{{key}}" bind:getList="getUserList" list="{{userList}}" total="{{userTotal}}" generic:selectable="user-item" >
</pagination>
複製代碼

具體應用能夠查看我寫的小demo:wxapp-pagination

相關文章
相關標籤/搜索