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

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

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

wxapp-pagination.gif

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

項目需求

具體項目需求:java

  • 查看本身相關的會議(頁面命名爲 meetings)
  • tab切換,分爲:git

    • 「個人會議」(我參加的會議,後面會以 "join" 爲 key區分)
    • 「個人預定」(我預定的會議,後面會以 "book" 爲 key區分)
  • 一次加載10條(size=10),拉到底部後,加載下一頁(page = page +1)

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

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

因此原型圖大概就長這樣:
image.pngjson

邏輯實現

與分頁邏輯相關的項目結構以下:小程序

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

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

image.png

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

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

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

// 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

相關文章
相關標籤/搜索