资讯专栏INFORMATION COLUMN

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

crelaber / 1320人阅读

摘要:项目中遇到切换列表,每个都需要分页的需求,分页流程具有相似性,于是想将分页封装为组件,方便应用。组件的复用完成了以上组件,在对其他分页的页面,可以直接复用。

项目中遇到 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 为分页操作
    }
  }
})


    
      
          
          
      
    

    
      
        
        
      
    
Page({
  onReachBottom(){
    const key = scene[+this.data.currentTab].key // 对应tab对应key
    this.setData({
      [key]: random(16)
    })
  }
})

分页组件的实现逻辑

触发到达底部之后,需要加载数据。但再加载之前,先满足加载的条件:

上一页还未加载完成(loading = true),不重复加载

当前页面全部加载完(ended = true),不继续加载

具体加载流程为:

page:触发 onReachBottom 事件,修改 pagination组件 key

component: key值监听到变化,触发加载事件 _loadMoreData

component: _loadMoreData 中判断满足条件后,触发加载列表函数 this.triggerEvent("getList"),并传入页面参数page 和 size。

page:向model层获取数据。

page:获取数据后,传递给 pagination组件listtotal 值。

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


循环列表item

pagination组件获取了可循环的列表,初始想法是循环slot,但是在slot中却获取不到 item 对象:


  

解决的办法是将每个列表项封装为组件,循环抽象节点,这样对其他页面的分页具有可拓展性。

componentGenerics 字段中声明:

// components/pagination/index.json
{
  "componentGenerics": {
    "selectable": true
  },
  // ...
}

使用抽象节点:



    

指定“selectable”具体是哪个组件:



对应 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。






组件的复用

完成了以上组件,在对其他分页的页面,可以直接复用。比如,实现一个获取全部用户列表的分页,只需要新增一个user-item的组件,像这样调用:


具体应用可以查看我写的小demo:wxapp-pagination。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105739.html

相关文章

  • Vue.js 实践(2):实现多条件筛选、搜索、排序及分页的表格功能

    摘要:基础布局的中主要为部分,分别是用于搜索筛选和分页的表单控件用于排序表格的表头以及用于展示数据的。这也是前瞻发布之后,提出废弃部分功能后许多人反应较为强烈的原因。 与上周的第一篇实践教程一样,在这篇文章中,我将继续从一种常见的功能——表格入手,展示Vue.js中的一些优雅特性。同时也将对filter功能与computed属性进行对比,说明各自的适用场景,也为vue2.0版本中即将删除的部...

    Profeel 评论0 收藏0
  • Vue.js 实践(2):实现多条件筛选、搜索、排序及分页的表格功能

    摘要:基础布局的中主要为部分,分别是用于搜索筛选和分页的表单控件用于排序表格的表头以及用于展示数据的。这也是前瞻发布之后,提出废弃部分功能后许多人反应较为强烈的原因。 与上周的第一篇实践教程一样,在这篇文章中,我将继续从一种常见的功能——表格入手,展示Vue.js中的一些优雅特性。同时也将对filter功能与computed属性进行对比,说明各自的适用场景,也为vue2.0版本中即将删除的部...

    ChanceWong 评论0 收藏0
  • 21 分钟学 apollo-client 系列:获取数据

    摘要:分钟学是一个系列,简单暴力,包学包会。一旦组件挂载后,会自动进行数据请求,前提是客户端提供的和后端的相符。如果回调返回直接不作请求。在组件内进行分页请求之前提到了,这个装饰器为添加了对象,其中有个函数为。 21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。 搭建 Apollo client 端,集成 redux使用 apollo-client 来获取数据修改本...

    robin 评论0 收藏0
  • CSS规范--BEM入门

    摘要:一开始,公司推出的,包括了规范以及其配套构建工具。代表的不同状态或不同版本。再来看一个之前用常规方式命名的的例子这些类名真是太不精确了,并不能告诉我们足够的信息。 这段时间在整理前端部分的代码规范,前面提到的CSS规范里面会涉及到选择器的命名,就参考BEM的命名规范,内容整理如下,供大家参考,请斧正!如大家有兴趣,可移步至CSS编码规范 BEM是由Yandex公司推出的一套CSS命名...

    li21 评论0 收藏0
  • SpringBoot-vue 基于Java的微服务全栈快速开发实践

    摘要:本项目将使用配合最简单的逻辑来展示一个基于的微服务全栈快速开发实践的。提供一系列大型项目常用的非功能性特征,比如内嵌服务器,安全,指标,健康检测,外部化配置。 SprintBoot-Vue SpringBoot + 前端MVVM 基于Java的微服务全栈快速开发实践 showImg(https://segmentfault.com/img/remote/1460000010167913...

    FleyX 评论0 收藏0

发表评论

0条评论

crelaber

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<