小程序分页实践:编写可复用分页组件

2019-07-17 admin

项目中遇到 tab切换列表,每个tab都需要分页的需求,分页流程具有相似性,于是想将分页封装为组件,方便应用。

组件的应用已写成一个小demo,效果如下图所示(数据用mock模拟):

wxapp-pagination.gif

源码可以查看:wxapp-pagination

项目需求

具体项目需求:

  • 查看自己相关的会议(页面命名为 meetings)

  • tab切换,分为:

    • “我的会议”(我参加的会议,后面会以 “join” 为 key区分)
    • “我的预约”(我预约的会议,后面会以 “book” 为 key区分)
  • 一次加载10条(size=10),拉到底部后,加载下一页(page = page +1)

当然,作为前端,要考虑性能方面的需求

  • 首次只加载默认tab页的首页,其他tab等到点击到对应tab才开始加载。
  • 回到已加载过的tab页,保留原数据不重新加载。

所以原型图大概就长这样: image.png

逻辑实现

与分页逻辑相关的项目结构如下:

├── components
│        ├── meeting-item      # 列表item
│        └── pagination            # 分页组件
├── model
│   └── user.js                    # 我的相关 model
└── pages
│       └── user                        # 我的相关页面
│       ├── meetings        # 我的会议(就是tab要分页的页面啦)
│       └── ...
│
└── vant-weapp

还是用图理一下他们之间的关系吧:

image.png

在组件内监听触发分页事件

触发分页的事件是滚动到页面的底部,小程序中,触发该事件是Page页面的onReachBottom事件,但是这个事件只能在Page中触发,所以要将这个触发时机传递给pagination组件。

解决思路是:组件 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

[转载]原文链接:https://segmentfault.com/a/1190000019793488

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-70003.html

文章标题:小程序分页实践:编写可复用分页组件

相关文章
React Native 用JavaScript编写原生ios应用
ReactNative 可以基于目前大热的开源JavaScript库React.js来开发iOS和Android原生App。而且React Native已经用于生产环境——Facebook Groups iOS 应用就是基于它开发的。 Re...
2015-11-12
ASP.NET 2.0 AJAX应用程序设计
ASP.NET Aiax技术是一种实现异步(Asynchronous)网络应用的技术,它被整合在ASP.NET 2.0之中,是As P.NET的一种扩展技术。通过ASENETAjax技术,开发人员或程序员可以将Web服务器控件和客户端脚本结...
2015-11-14
如何编写干净高效的CSS代码
其实CSS的学习并不困难,但在一些较为大型的项目中就显得杂乱无章,变得很难管理,尤其是不同的人编写CSS的风格总会略有不同,从团队合作的层面上来说,就更加难以沟通,所以,我们为此总结了一些如何实现高效整洁的CSS代码原则: 使用Reset但...
2015-11-12
百度新功能【特效搜索】演示 惊呆了小伙伴
百度搜索最近又出新玩意新功能了,可能你还没有发现,名为【百度特效搜索】已经默默上线了,有什么效果呢? 在百度搜索中根据用户搜索的关键词来出发某些动作,例如笔者搜索“打雷”关键字,在搜索结果中你会听到有打雷声, 黑洞,闪烁、翻转、跳跃,打雷,...
2015-11-12
mpvue 小程序如何开启下拉刷新,上拉加载?
https://developers.weixin.qq.com/miniprogram/dev/api/pulldown.html#onpulldownrefresh 小程序API 微信小程序之下拉加载和上拉刷新 微信小程序下拉加载和上拉...
2018-05-25
详解angular2封装material2对话框组件
1. 说明 angular-material2自身文档不详,控件不齐,使用上造成了很大的障碍。这里提供一个方案用于封装我们最常用的alert和confirm组件。 2. 官方使用方法之alert ①编写alert内容组件 @Componen...
2017-03-13
微信公众号实现无限制推送模板消息!可向指定openID群发
微信认证的服务号才有推送模板消息接口 所以本文需要在认证服务号的情况下学习 以上就是模板消息,只有文字和跳转链接,没有封面图。 在服务号的后台添加功能插件-模板消息即可。 模板消息,都是在后台选择一个群发模板的,然后获取模板ID,根据这...
2018-01-14
7个你可能不认识的CSS单位
浼楁墍鍛ㄧ煡CSS鎶€鏈�鎴戜滑铏界劧寰堢啛鎮夛紝鍦ㄤ娇鐢ㄧ殑杩囩▼鍗村緢瀹规槗琚�鍥颁綇锛岃繖璁╂垜浠�鍦ㄦ柊闂�棰樺嚭鐜扮殑鏃跺€欏彉寰楀緢涓嶅埄銆� 闅忕潃web缁х画涓嶆柇鍦板彂灞曪紝瀵逛簬鏂版妧鏈�鏂拌В鍐虫柟妗堢殑瑕佹眰涔熶細涓...
2015-11-11
HTML5:未来不可阻挡的力量
HTML5像是互联网行业扔下的一颗炸弹,有些人还未反应过来,原先专注的领域也许就将面临彻底的革新。比如像前几年疯狂甚至有点野蛮成长的App。虽然HTML5从出现到去年标准尘埃落定,一直在争议中曲折发展,但是各种H5页面的推广以及H5小游戏在...
2015-11-12
回到顶部