likes
comments
collection
share

从复杂到简单:BaseTablePresenter让前端表格操作更加轻松

作者站长头像
站长
· 阅读数 18

在上篇文章中我们讲了如何实现一个下拉刷新,上拉加载,带有搜索功能的列表状态管理器

今天讲一个管理后台常用的功能,表格功能

需求分析

首先看看下面的表格页面,有筛选,分页,渲染等功能

从复杂到简单:BaseTablePresenter让前端表格操作更加轻松

根据这些功能我们可以定义出页面视图需要的数据模型

视图数据模型

首先是分页相关的数据

interface Pagination {
  current: number;
  total: number;
  pageSize: number;
}
  • data: 每一行的数据
  • params: 请求参数
  • loading: ...
export interface TableState<Row = any> {
  loading: boolean;
  data: Row[];
  params: Record<any, any>;
  pagination: Pagination & Record<string, any>;
  [p: string]: any;
}

有了这些数据就可以满足一个表格的正常渲染了

视图操作方法

接下来定义一些表格需要的操作方法

loading控制

这里我们用两个方法来控制loading的切换

  showLoading() {
    if (this._loadingCount === 0) {
      this.setState((s) => {
        s.loading = true;
      });
    }
    this._loadingCount += 1;
  }

  hideLoading() {
    this._loadingCount -= 1;
    if (this._loadingCount === 0) {
      this.setState((s) => {
        s.loading = false;
      });
    }
  }

fetchData

获取数据的方法,这里我们定义一个异步函数,声明好接口定义,函数内容直接抛错,需要等具体使用在去实现真正的fetchData

 async fetchData(
    params: Partial<TableState['params']> & {
      current: number;
      pageSize: number;
    },
  ): Promise<{
    data: any[];
    current: number;
    pageSize: number;
    total: number;
  }> {
    throw Error('请实现fetchTable');
  }

updateData

updateData方法用来调用fetchData,更新数据,切换loading

  /**
   * 发请求,更新数据
   * @returns
   */
  updateData() {
    const params: Record<any, any> = {};
    Object.entries(this.state.params || {}).forEach(([k, v]) => {
      if (v !== undefined) {
        Object.assign(params, { [k]: v });
      }
    });
    this.showLoading();

    return this.fetchData({
      current: this.state.pagination.current || 1,
      pageSize: this.state.pagination.pageSize || 10,
      ...params,
    })
      .then((res) => {
        this.setState((s) => {
          s.pagination.current = res.current;
          s.pagination.pageSize = res.pageSize;
          s.pagination.total = res.total;
          s.data = res.data;
        });
        return res;
      })
      .finally(() => {
        this.hideLoading();
      });
  }

setPagination

setPagination,顾名思义就是更新分页状态的方法,这里有一个潜在的逻辑,当pageSize修改的时候,分页要切换为第一页

/**
   * 更新参数,如果修改的参数是不是current,重置current为1
   * @param pagination
   */
  setPagination(pagination: Partial<Pagination>) {
    this.setState((s) => {
      let current = pagination.current || s.pagination.current;

      if (pagination?.pageSize) {
        if (pagination.pageSize !== s.pagination.pageSize) {
          current = 1;
        }
      }

      s.pagination = {
        ...s.pagination,
        ...pagination,
        current,
      };
    });
  }

参数设置

下面是更改参数和重置参数的方法

  /**
   * 更新参数,重置当前请求页面为1
   * @param params
   */
  setParams(params: Partial<TableState['params']>) {
    const d: Partial<TableState['params']> = {};
    Object.entries(params).forEach(([k, v]) => {
      if (v !== undefined) {
        Object.assign(d, {
          [k]: v,
        });
      }
    });

    this.setState((s) => {
      s.params = {
        ...s.params,
        ...d,
      };

      s.pagination.current = 1;
    });
  }

  resetParams() {
    this.setState((s) => {
      s.params = {} as Record<any, any>;
    });
  }

分页状态修改

很多时候我们都是用回调函数去监听ui组件的分页状态修改,这里也提供一个对应的方法

  /**
   * 切换页面,并且更新数据
   * @param p
   */
  onPageChange(p: Pagination) {
    this.setPagination(p);

    return this.updateData();
  }

完整的代码例子

完整的代码例子可以查看github仓库,记得给个star哈

如何使用

npm install @clean-js/pro-presenters
import React from 'react';
import { usePresenter } from '@clean-js/react-presenter';
import { BaseTablePresenter } from '@clean-js/pro-presenters';

export const demo = () => {
  const { presenter, state } = usePresenter(BaseTablePresenter);
  return (
    <Table
      loading={state.loading}
      columns={state.columns}
      pagination={state.pagination}
      dataSource={state.data}
      onChange={(p) => {
        presenter.onPageChange?.(p);
      }}
    />
  );
};

当我们有多个表格要使用的时候,初始化多个presenter即可 下面的两个usePresenter会返回不同的实例

import React from 'react';
import { usePresenter } from '@clean-js/react-presenter';
import { BaseTablePresenter } from '@clean-js/pro-presenters';

export const demo = () => {
    const { presenter, state } = usePresenter(BaseTablePresenter);
    const { p: tableP } = usePresenter(BaseTablePresenter);
    return (
        <div>
             <Table
              loading={state.loading}
              columns={state.columns}
              pagination={state.pagination}
              dataSource={state.data}
              onChange={(p) => {
                presenter.onPageChange?.(p);
              }}
            />
             <Table
              loading={tableP.state.loading}
              columns={tableP.state.columns}
              pagination={tableP.state.pagination}
              dataSource={tableP.state.data}
              onChange={(p) => {
                tableP.onPageChange?.(p);
              }}
            />
        </div>
    );
};

如何拓展功能

假设我们需要添加一个columns属性,用来配置UI,我们可以继承这个BaseTablePresenter,对其进行扩展即可

type Columns = { key: string }[];

class CustomTableP extends BaseTablePresenter<
  { name: string },
  { columns: Columns }
> {
  constructor() {
    super({
      data: [],
      loading: false,
      params: {},
      pagination: {
        current: 1,
        pageSize: 10,
        total: 1,
      },
      columns: [],
    });
  }

  setColumns(columns: Columns) {
    this.setState((s) => {
      s.columns = columns;
    });
  }

  test() {
    return 'test';
  }
}
export const demo = () => {
  const { presenter, state } = usePresenter(CustomTableP);
  return (
    <Table
      loading={state.loading}
      columns={state.columns}
      pagination={state.pagination}
      dataSource={state.data}
      onChange={(p) => {
        presenter.onPageChange?.(p);
      }}
    />
  );
};

npm包地址 github地址,记得给个star哈