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


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 相关文章推荐
优秀js开源框架-jQuery使用手册(1)
Mar 10 Javascript
使用jQuery轻松实现Ajax的实例代码
Aug 16 Javascript
JS中令人发指的valueOf方法介绍
Feb 22 Javascript
Javascript aop(面向切面编程)之around(环绕)分析
May 01 Javascript
基于jQuery插件实现环形图标菜单旋转切换特效
May 15 Javascript
javaScript如何跳出多重循环break、continue
Sep 01 Javascript
解决URL地址中的中文乱码问题的办法
Feb 10 Javascript
AngularJS中下拉框的高级用法示例
Oct 11 Javascript
vue2.0 常用的 UI 库实例讲解
Dec 12 Javascript
nuxt.js中间件实现拦截权限判断的方法
Nov 21 Javascript
vue实现图片预览组件封装与使用
Jul 13 Javascript
JavaScript ECMA-262-3 深入解析(二):变量对象实例详解
Apr 25 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
phpmyadmin 3.4 空密码登录的实现方法
2010/05/29 PHP
PHP循环输出指定目录下的所有文件和文件夹路径例子(简单实用)
2014/05/10 PHP
php上传大文件设置方法
2016/04/14 PHP
PHP面向对象程序设计之命名空间与自动加载类详解
2016/12/02 PHP
php探针不显示内存解决方法
2019/09/17 PHP
jQuery 第二课 操作包装集元素代码
2010/03/14 Javascript
JavaScript 轻松搞定快捷留言功能 只需一行代码
2010/04/01 Javascript
JavaScript词法作用域与调用对象深入理解
2012/11/29 Javascript
addEventListener和attachEvent二者绑定的执行函数中的this不相同
2012/12/09 Javascript
JavaScript控制Session操作方法
2013/01/17 Javascript
一个JavaScript去除字符串末尾的空白实例代码
2014/09/22 Javascript
如何编写高质量JS代码
2014/12/28 Javascript
JS实现样式清新的横排下拉菜单效果
2015/10/09 Javascript
Bootstrap4一次重大更新 几乎涉及每行代码
2016/05/16 Javascript
js实现的光标位置工具函数示例
2016/10/03 Javascript
JS键盘版计算器的制作方法
2016/12/03 Javascript
详解Html a标签中href和onclick用法、区别、优先级别
2017/01/16 Javascript
Nodejs多站点切换Htpps协议详解及简单实例
2017/02/23 NodeJs
JS 实现发送短信验证码的“59秒后重新发送验证短信”功能
2019/08/23 Javascript
深入浅析Python获取对象信息的函数type()、isinstance()、dir()
2018/09/17 Python
python http基本验证方法
2018/12/26 Python
python Pandas库基础分析之时间序列的处理详解
2019/07/13 Python
使用Python快乐学数学Github万星神器Manim简介
2019/08/07 Python
Pytorch Tensor的统计属性实例讲解
2019/12/30 Python
css3弹性盒模型(Flexbox)详细介绍
2014/10/08 HTML / CSS
巴西葡萄酒销售网站:Wine.com.br
2017/11/07 全球购物
印度手工编织服装和家居用品商店:Fabindi
2019/10/07 全球购物
什么是servlet
2012/05/08 面试题
药店主任岗位责任制
2014/02/10 职场文书
学习决心书
2014/03/11 职场文书
清明节演讲稿
2014/05/27 职场文书
预备党员群众路线思想汇报2014
2014/10/25 职场文书
2014年电话客服工作总结
2014/12/09 职场文书
学校学期工作总结
2015/08/13 职场文书
高考升学宴主持词
2019/06/21 职场文书
从贫穷到富有,是知识技能和学习力的差别
2019/08/20 职场文书