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


Posted in Javascript onJuly 18, 2019

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

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

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

源码可以查看:wxapp-pagination

项目需求

具体项目需求:

  • 查看自己相关的会议(页面命名为 meetings)
  • tab切换,分为:
    • “我的会议”(我参加的会议,后面会以 "join" 为 key区分)
    • “我的预约”(我预约的会议,后面会以 "book" 为 key区分)
  • 一次加载10条(size=10),拉到底部后,加载下一页(page = page +1)

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

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

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

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

逻辑实现

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

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

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

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

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

触发分页的事件是滚动到页面的底部,小程序中,触发该事件是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组件list和total 值。
  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。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript脚本语言在网页中的简单应用
May 13 Javascript
window.ActiveXObject使用说明
Nov 08 Javascript
Javascript学习笔记-详解in运算符
Sep 13 Javascript
AngularJS实现表单验证
Jan 28 Javascript
Jquery修改image的src属性,图片不加载问题的解决方法
May 17 Javascript
JavaScript三种绑定事件方式及相互之间的区别分析
Jan 10 Javascript
JavaScript中运算符规则和隐式类型转换示例详解
Sep 06 Javascript
webpack打包react项目的实现方法
Jun 21 Javascript
jQuery实现checkbox全选、反选及删除等操作的方法详解
Aug 02 jQuery
layui 实现自动选择radio单选框(checked)的方法
Sep 03 Javascript
JavaScript创建表格的方法
Apr 13 Javascript
原生js实现简单轮播图
Oct 26 Javascript
在vue项目中使用sass语法问题
Jul 18 #Javascript
微信小程序用户授权、位置授权及获取微信绑定手机号
Jul 18 #Javascript
EasyUI 数据表格datagrid列自适应内容宽度的实现
Jul 18 #Javascript
Vue之beforeEach非登录不能访问的实现(代码亲测)
Jul 18 #Javascript
jquery实现下载图片功能
Jul 18 #jQuery
jQuery实现图片下载代码
Jul 18 #jQuery
使用react context 实现vue插槽slot功能
Jul 18 #Javascript
You might like
Godaddy空间Zend Optimizer升级方法
2010/05/10 PHP
使用PHP Socket写的POP3类
2013/10/30 PHP
基础的WordPress插件制作教程
2015/11/24 PHP
laravel 创建命令行命令的图文教程
2019/10/23 PHP
JS控件autocomplete 0.11演示及下载 1月5日已更新
2007/01/09 Javascript
Javascript----文件操作
2007/01/18 Javascript
Javascript技巧之不要用for in语句对数组进行遍历
2010/10/20 Javascript
jQuery实现 注册时选择阅读条款 左右移动
2013/04/11 Javascript
JavaScript运行机制之事件循环(Event Loop)详解
2014/10/10 Javascript
JavaScript中的console.assert()函数介绍
2014/12/29 Javascript
jQuery表单验证功能实例
2015/08/28 Javascript
jQuery form 表单验证插件(fieldValue)校验表单
2016/01/24 Javascript
学习使用Bootstrap输入框、导航、分页等常用组件
2017/05/11 Javascript
基于ajax和jsonp的原生封装(实例)
2017/10/16 Javascript
Vue项目部署的实现(阿里云+Nginx代理+PM2)
2019/03/26 Javascript
原生JS 实现的input输入时表格过滤操作示例
2019/08/03 Javascript
CentOS 8.2服务器上安装最新版Node.js的方法
2020/12/16 Javascript
分析python服务器拒绝服务攻击代码
2014/01/16 Python
使用Python脚本对Linux服务器进行监控的教程
2015/04/02 Python
使用PyInstaller将Python程序文件转换为可执行程序文件
2016/07/08 Python
centos6.7安装python2.7.11的具体方法
2017/01/16 Python
python下setuptools的安装详解及No module named setuptools的解决方法
2017/07/06 Python
python连接数据库的方法
2017/10/19 Python
Python/Django后端使用PIL Image生成头像缩略图
2019/04/30 Python
python list数据等间隔抽取并新建list存储的例子
2019/11/27 Python
计算机专业毕业生自荐信
2013/12/31 职场文书
成人继续教育实施方案
2014/03/01 职场文书
“学雷锋活动月”总结
2014/03/09 职场文书
财务部总监岗位职责
2014/03/12 职场文书
暑期政治学习心得体会
2014/09/02 职场文书
小学语文教师年度考核个人总结
2015/02/05 职场文书
2015年学校心理健康教育工作总结
2015/05/11 职场文书
女方家长婚礼致辞
2015/07/27 职场文书
利用python Pandas实现批量拆分Excel与合并Excel
2021/05/23 Python
mysql 带多个条件的查询方式
2021/06/05 MySQL
请求模块urllib之PYTHON爬虫的基本使用
2022/04/08 Python