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


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 获取网页参数系统
Jul 19 Javascript
jquery数组过滤筛选方法grep()简介
Jun 06 Javascript
使用text方法获取Html元素文本信息示例
Sep 01 Javascript
node.js中watch机制详解
Nov 17 Javascript
javascript实现的固定位置悬浮窗口实例
Apr 30 Javascript
javascript实现简单的全选和反选功能
Jan 05 Javascript
ionic js 模型 $ionicModal 可以遮住用户主界面的内容框
Jun 06 Javascript
jquery.form.js异步提交表单详解
Apr 25 jQuery
浅谈es6语法 (Proxy和Reflect的对比)
Oct 24 Javascript
Vue-路由导航菜单栏的高亮设置方法
Mar 17 Javascript
微信小程序按钮点击动画效果的实现
Sep 04 Javascript
JavaScript如何实现监听键盘输入和鼠标监点击
Jul 20 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
php 代码优化的42条建议 推荐
2009/09/25 PHP
Laravel Eloquent分表方法并使用模型关联的实现
2019/11/25 PHP
不错的JS中变量相关的细节分析
2007/08/13 Javascript
JS 如果改变span标签的是否隐藏属性
2011/10/06 Javascript
JS图片无缝、平滑滚动代码
2014/03/11 Javascript
当某个文本框成为焦点时即清除文本框内容
2014/04/28 Javascript
jquery实现当滑动到一定位置时固定效果
2014/06/17 Javascript
javascript实现行拖动的方法
2015/05/27 Javascript
js性能优化技巧
2015/11/29 Javascript
简单的JS时钟实例讲解
2016/01/13 Javascript
使用Script元素发送JSONP请求的方法
2016/06/12 Javascript
JS控制静态页面之间传递参数获取参数并应用的简单实例
2016/08/10 Javascript
jquery遍历标签中自定义的属性方法
2016/09/17 Javascript
jQuery基于ajax方式实现用户名存在性检查功能示例
2017/02/10 Javascript
gulp加批处理(.bat)实现ng多应用一键自动化构建
2017/02/16 Javascript
jquery-ui 进度条功能示例【测试可用】
2019/07/25 jQuery
layui数据表格跨行自动合并的例子
2019/09/02 Javascript
Vue使用Clipboard.JS在h5页面中复制内容实例详解
2019/09/03 Javascript
vue父组件给子组件的组件传值provide inject的方法
2019/10/23 Javascript
通过实例解析javascript Date对象属性及方法
2020/11/04 Javascript
SpringBoot在yml配置文件中配置druid的操作
2020/11/16 Javascript
在python中以相同顺序shuffle两个list的方法
2018/12/13 Python
对python判断ip是否可达的实例详解
2019/01/31 Python
python3+django2开发一个简单的人员管理系统过程详解
2019/07/23 Python
python opencv 检测移动物体并截图保存实例
2020/03/10 Python
Python实现Appium端口检测与释放的实现
2020/12/31 Python
Canvas 像素处理之改变透明度的实现代码
2019/01/08 HTML / CSS
Contém1g官网:巴西彩妆品牌
2020/01/17 全球购物
美国折扣香水网站:The Perfume Spot
2020/12/12 全球购物
小学生考试获奖感言
2014/01/30 职场文书
现场施工员岗位职责
2014/03/10 职场文书
2014年3.15团委活动总结
2014/03/16 职场文书
2015年工程师工作总结
2015/04/30 职场文书
公安纪律作风整顿心得体会
2016/01/23 职场文书
python如何获取网络数据
2021/04/11 Python
Redis中缓存穿透/击穿/雪崩问题和解决方法
2021/12/04 Redis