小程序触底加载更多内容的实现

首先看看展现效果

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一旦被注册到当前组件,其中的各配置将都会与组件的配置项进行合并,因此能够直接使用。

总结

当前的作法只是一个简单的例子,有时候咱们会不少地方须要调用不一样的接口,但以上例子中的作法并不支持,你能够根据本身须要,将这个封装作得更加灵活,以达到适应本身项目的目的。

相关文章
相关标签/搜索