最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 科技 - 知识百科 - 正文

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

来源:动视网 责编:小采 时间:2020-11-27 21:53:40
文档

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

小程序分页实践之编写可复用分页组件:项目中遇到 tab切换列表,每个tab都需要分页的需求,分页流程具有相似性,于是想将分页封装为组件,方便应用。 组件的应用已写成一个小demo,效果如下图所示(数据用mock模拟): 源码可以查看:wxapp-pagination 项目需求 具体项目需求: 查看自己相
推荐度:
导读小程序分页实践之编写可复用分页组件:项目中遇到 tab切换列表,每个tab都需要分页的需求,分页流程具有相似性,于是想将分页封装为组件,方便应用。 组件的应用已写成一个小demo,效果如下图所示(数据用mock模拟): 源码可以查看:wxapp-pagination 项目需求 具体项目需求: 查看自己相


项目中遇到 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。

    文档

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

    小程序分页实践之编写可复用分页组件:项目中遇到 tab切换列表,每个tab都需要分页的需求,分页流程具有相似性,于是想将分页封装为组件,方便应用。 组件的应用已写成一个小demo,效果如下图所示(数据用mock模拟): 源码可以查看:wxapp-pagination 项目需求 具体项目需求: 查看自己相
    推荐度:
    标签: 小程序 组件 分页
    • 热门焦点

    最新推荐

    猜你喜欢

    热门推荐

    专题
    Top