小程序开发中,列表分页组件是比较常见的需求,每次都要写 totalNum、pageSize、loading 状态等,写得多了,始终觉得很麻烦,萌生了封装一个容器组件的念头。

需求

  1. 封装列表分页组件,包含加载状态
  2. 尽量少的暴露接口,减少可配置项
  3. 接口尽量少的同时,提供必要的数据重置方式,方便用户使用时候,稀奇古怪的需求

基础知识

ref

ref 获取组件实例 - 支付宝开放平台
和vue中概念基本一致,可以通过 ref 获取实例的属性,从而修改组件数据的状态。

slot

组件模板和样式 - 支付宝开放平台
一般 slot 我们只是使用插入数据,很少通过 slot 获取内部数据。在支付宝小程序中可以通过作用域插槽 slot-scope ,实现外部对内部的数据访问。

组件实现

<view>
  <slot name="item" list="&#123;&#123;list&#125;&#125;"></slot>
  <slot name="empty" a:if="&#123;&#123;init && !totalNum&#125;&#125;">
    <view>暂无数据</view>
  </slot>
  <slot name="loading" a:if="&#123;&#123;init && !finished&#125;&#125;">
    <view class="loading">
      <view class="text">加载中...</view>
    </view>
  </slot>
  <slot name="finished" a:if="&#123;&#123; totalNum && finished&#125;&#125;">
    <view class="finished">
      <view class="line"></view>
      <view class="text">没有更多了</view>
      <view class="line"></view>
    </view>
  </slot>
</view>
import &#123; Component &#125; from 'herbjs';

interface IComponentData &#123;&#125;

interface IComponentProps &#123;
  onGetData(currentPage: number, pageSize: number): void;

  pageSize: number;
&#125;

interface IComponentMethods &#123;&#125;

interface IData &#123;
  list: Array<any>;
  totalNum: number;
&#125;

/**
 * Generics
 * @example
 *    Component<Data, Props, Methods, PageStore, AppStore>
 */
Component<IComponentData, IComponentProps, IComponentMethods>(&#123;
  data: &#123;
    list: [],
    currentPage: 0, // 现在页面
    totalNum: 0, // 总条数
    totalPage: 0, // 总页数
    init: false,
    finished: false,
    loadingFlag: false,
  &#125;,
  props: &#123;
    onGetData: () => &#123;&#125;,
    pageSize: 20,
  &#125;,
  didMount() &#123;&#125;,
  didUnmount() &#123;&#125;,
  methods: &#123;
    getData() &#123;
      let &#123; currentPage, finished, loadingFlag &#125; = this.data;
      if (finished || loadingFlag) return;
      this.setData(&#123;
        loadingFlag: true,
      &#125;);
      return new Promise((resolve, reject) => &#123;
        resolve(this.props.onGetData(++currentPage, this.props.pageSize));
      &#125;).then((res: IData) => &#123;
        console.log(res);
        const totalPage = Math.ceil(res.totalNum / this.props.pageSize);
        if (currentPage === 1) &#123;
          this.setData(&#123;
            currentPage,
            list: res.list,
            init: true,
            totalPage,
            totalNum: res.totalNum,
            loadingFlag: false,
            finished:
              totalPage === 0 || (totalPage && currentPage === totalPage),
          &#125;);
        &#125; else &#123;
          this.setData(&#123;
            loadingFlag: false,
            currentPage,
            finished: totalPage && currentPage === totalPage,
            list: [...this.data.list, ...res.list],
          &#125;);
        &#125;
      &#125;);
    &#125;,
  &#125;,
&#125;);

对外部暴露的接口

接口 作用
slot name:item list 需要循环的 list 数据
slot name:loading 修改loading样式
slot name:finished 修改 finished 样式
props onGetData 获取外部数据
props pageSize 获取分页大小,默认 20
Methods getData 调用获取外部数据

使用:

<view>
  <waterflow-list ref='saveComponentLoadData' onGetData="getListData">
    <view slot="item" slot-scope="props">
      <block a:for="&#123;&#123;props.list&#125;&#125;">
        <view>&#123;&#123;item.num&#125;&#125;</view>
      </block>
    </view>
  </waterflow-list>
</view>
Page<IPageState, IPageMethods, IPageStore>(&#123;
  onLoad(options) &#123;
    this.init();
  &#125;,
  onReady() &#123;
    this.addData();
  &#125;,
  onShow() &#123;&#125;,
  onHide() &#123;&#125;,
  onUnload() &#123;&#125;,
  init() &#123;
    this.dispatch('beforeRender');
  &#125;,
  // 保存 ref
  saveComponentLoadData(ref) &#123;
    this.loadDataComponent = ref;
  &#125;,
  // 获取数据
  getListData(currentPage, pageSize) &#123;
    return &#123;
      list: [&#123; num: 1 &#125;, &#123; num: 2 &#125;, &#123; num: 3 &#125;],
      totalNum: 3,
    &#125;;
  &#125;,
  // 手动触发获取数据
  addData() &#123;
    this.loadDataComponent.getData();
  &#125;,
&#125;);

核心思想

通过获取组件 ref, 调用组件内部的 getData 方法,反射到主页面的getListData实现数据获取。
在通过 slot 的 slot-scope 获取需要展示的 list 数据,外部进行遍历并设置样式。
何时加载数据,由外部实现和决定,组件本身不会主动去请求数据。

  1. 本文小程序指的是支付宝小程序,微信小程序好像并没有 slot-scope 功能
  2. 框架使用 herbjs

完整代码:
mini-alipay-example/src/components/waterflow-list at main · Yaob1990/mini-alipay-example