小程序觸底加載更多內容的實現

首先看看展現效果

Tips:下面gif圖2.7MB左右,網絡很差可能加載有問題(沒法打開請點擊此圖片連接單獨查看javascript

實現思路

上拉加載更多的細節:html

  1. 觸底: 監測觸底事件在觸底以後執行一系列動做
  2. 加載數據: 在觸底後須要向服務器請求數據,若是已經請求到了全部數據,應該再也不發送請求。
  3. 加載狀態: 請求數據的等待時間,須要更新狀態爲加載中,數據渲染完成後取消該狀態的顯示
  4. 數據渲染: 將請求到的數據顯示在視圖中
  5. 沒有更多數據的提示

優化項vue

  1. 防止連續的屢次請求
  2. 封裝:如何在多個頁面應用同一套實現代碼

功能的實現

1 數據結構的肯定

數據結構來源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
        }
    ]
}
複製代碼

2 ajax與後端的模擬

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

3 loading組件的封裝

一個項目的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

4 data的肯定

data: {
    loadingStatus: true,  // loading狀態(加載中/無數據)的控制
    loadingShow: false, // loading組件的顯示控制
    products: [], // 展現的數據
    productModel: null, // Products類建立的對象模型
    currentPage: 1, // 當請求頁的設置
    pageCount: 5 // 每頁請求數據的數量
  },

複製代碼

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提供的瀑布流組件進行數據的渲染。小程序

6 觸底加載更多數據與請求的優化

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語句,剩餘代碼判斷了當前展現的數據是否是最後一頁的數據:

  • 若是是的話就再也不進行數據的請求,並將loading組件顯示出來,loading狀態設爲false,進行沒有更多數據提示的相關展現。
  • 若是當前展現的數據沒有到最後一頁,則應請求下一頁數據,並將loading組件加載出來,loading狀態爲加載狀態。這裏使用setTimeout模擬了發送和接收請求這段等待的時間。

getPorductList方法裏對(模擬的)後端進行了請求並作了數據設置,以後調用renderWaterFlow進行瀑布流的展現,在lin-ui瀑布流函數的回調中,能夠設置將loadingShowfalse隱藏loading組件。

連續請求的優化

上面提到先忽略onReachBottom最外層的if語句,這裏來看看這個if語句解決了什麼問題,上面代碼中能夠看到有兩個console打印語句,一個是觸底一個是請求,當網絡稍微差的時候,咱們能夠在沒有接收到請求數據的時候觸發屢次觸底事件,這是不合理的,因此加了這個if語句,判斷是否在loading了,若是在loading,則代表正在請求數據,就不該該再發送請求,不然再繼續進行請求的邏輯。

7 封裝的考慮

上面的功能已經完成,但還能夠作不少優化,好比最起碼在onLoadonReachBottom寫不少代碼看起來讓人很不舒服。 但更重要的並非這個問題,而是咱們這個觸底請求數據可能要在多個頁面中用到,如何只寫一份代碼就能讓不一樣的頁面使用這個功能就顯得很重要了。

在接觸這個做業時對說起的封裝本身感受並無什麼地方值得封裝,由於事實上代碼量並非不少,對量不是不少的代碼不能爲了封裝而封裝吧,想了想它的需求,才明白應該提取公用的部分,將其封裝起來。

這裏的方式是使用behaviorsbehavior就相似vue中的mixin(本身沒寫太小程序,看了文檔後感受這兩種東西做用很是類似,將二者作類比能夠更方便本身對它的理解)

8 借封裝優化代碼(踩坑之旅)

  1. behavior 不能在Page中使用
  2. Page有本身的用處,Component不能替代它,如觸底函數在Component中是沒有的
  3. behaviorComponent的生命週期函數不一樣於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();
    }
  },
})

複製代碼

這裏的封裝其實就是將以前頁面中的函數進行移植,首先將原來的數據能夠徹底剪切過來,以前頁面的renderWaterFlowgetPorductList方法複製到behaviormethods字段中,onLoad中的代碼能夠徹底提取出來寫一個initData()方法,onReachBottom中的代碼提取出來寫成一個handleReachBottom()方法,將這兩個方法也複製到behavior中的methods字段。到這裏代碼的移植工做就作完大部分了。

而後看看獲取第一組數據的執行時機,在Page中的時候是放在onLoad方法中的,如今應該放到attached方法中,attachedbehavior的一個生命週期方法,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,雖然會觸發組件中事件的監聽,但因爲觸底時候會設置reachBottomtrue,咱們就能夠將此次false過濾掉,若是觸發過來的都是true,咱們就認爲觸發了觸底事件,而後執行handleReachBottom

這裏能夠直接在Component中執行引入過來的behavior,引入過來的behavior一旦被註冊到當前組件,其中的各配置將都會與組件的配置項進行合併,因此能夠直接使用。

總結

當前的作法只是一個簡單的例子,有時候咱們會不少地方須要調用不一樣的接口,但以上例子中的作法並不支持,你能夠根據本身須要,將這個封裝作得更加靈活,以達到適應本身項目的目的。

相關文章
相關標籤/搜索