Ant Design 4.0更新指南(1)——ProTable

3,875 阅读4分钟

作为React技术栈的坚定拥护者,一定离不开阿里爸爸的大怀抱。作为国内最好用的组件库【之一】的Ant Design迎来个跨版本大更新,年后复工后我在第一时间便进行了尝鲜,刚好公司有老项目重构,在初步了解之后,决定尝试在新项目中使用And Design 4.0进行开发,与之对应的Ant Design Pro也更新到了4.0版本,Ant Design Pro是给予Ant Design的开箱即用中台前端/设计解决方案。简单的说,Ant Dsign是砖头,那Ant Design Pro就是用这些砖头垒起来的框架,你可以基于这个框架来按照你自己的想法重新装修,也可以在不破坏主体结构的情况下拆除部分内容重建。举个例子,对于我来说,原框架集成的国际化部分是无用的,那我就需要剔除。本系列文章主要针对在项目中使用Ant Design 4.0中遇到的一下值得注意的点~

本片先来说一下让我很是喜欢的ProTable这个新组件,让我们一起来看看它到底Pro在哪里。

以往,我们在做列表功能之类,需要重复的去手动的在componentDidMount钩子写请求数据,然后把数据重model中取出来,然后再放到Table组件的dataSource属性中,然后还要处理pageChange事件,然后还要手动的去渲染搜索项,然后就又得自己去引入各种组件,然后又各种堆接节点...这个过程下来,人都要惆怅咯。再加之一般做这种中后台管理系统,类似的功能很多,重复且低效的工作方式简直无法忍受。

import React, { PureComponent, Fragment } from 'react';
import { connect } from 'dva';
import { Row, Col, Card, Form, Input, Button, DatePicker, Divider, message, Table } from 'antd';
import router from 'umi/router';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import { formatDate, getStartTimeStamp, getEndTimeStamp } from '@/utils/utils';
import styles from './style.less';

const FormItem = Form.Item;
const { RangePicker } = DatePicker;
const getValue = obj =>
  Object.keys(obj)
    .map(key => obj[key])
    .join(',');

/* eslint react/no-multi-comp:0 */
@connect(({ approval, studentManagment, loading }) => ({
  approval,
  studentManagment,
  loading: loading.effects['studentManagment/fetch'],
}))
@Form.create()
class StudentManagement extends PureComponent {
  state = {
    selectedRows: [],
    formValues: {},
  };

  columns = [
    {
      title: '名称',
      dataIndex: 'name',
      render: val => val || '-',
    },
    {
      title: '创建日期',
      dataIndex: 'createTime',
      render: val => val && formatDate(val),
    },    {
      title: '性别',
      dataIndex: 'sex'
    },
    {
      title: '年龄',
      dataIndex: 'age'
    },
    {
      title: '学校',
      dataIndex: 'school'
    },
    {
      title: '年级',
      dataIndex: 'grade'
    },
    {
      title: '操作',
      render: (text, record) => (
        <Fragment>
          <a
            onClick={() =>
              router.push({
                pathname: '/common/landing-page-management/edit',
                query: {
                  id: record.id,
                },
              })
            }
          >
            编辑
          </a>
          <Divider type="vertical" />
          <a onClick={() => this.onCopyLink(record)}>复制链接</a>
        </Fragment>
      ),
    },
  ];

  componentWillMount() {
    this.getData();
  }

  getData = params => {
    const { dispatch } = this.props;
    dispatch({
      type: 'studentManagment/fetch',
      payload: params,
    });
  };

  onCopyLink = record => {
    const input = document.createElement('input');
    input.value = record.shortUrl;
    document.body.appendChild(input);
    input.select();
    input.setSelectionRange(0, input.value.length);
    document.execCommand('Copy');
    document.body.removeChild(input);
    message.success('复制成功!');
  };

  handleStandardTableChange = (pagination, filtersArg, sorter) => {
    const { formValues } = this.state;

    const filters = Object.keys(filtersArg).reduce((obj, key) => {
      const newObj = { ...obj };
      newObj[key] = getValue(filtersArg[key]);
      return newObj;
    }, {});

    const params = {
      page: pagination.current,
      pageSize: pagination.pageSize,
      ...formValues,
      ...filters,
    };
    if (sorter.field) {
      params.sorter = `${sorter.field}_${sorter.order}`;
    }
    this.getData(params);
  };

  handleFormReset = () => {
    const { form } = this.props;
    form.resetFields();
    this.setState({
      formValues: {},
    });
    this.getData();
  };

  handleSelectRows = rows => {
    this.setState({
      selectedRows: rows,
    });
  };

  handleSearch = e => {
    e.preventDefault();

    const { form } = this.props;

    form.validateFields((err, fieldsValue) => {
      if (err) return;

      const values = {
        name: fieldsValue.name,
        startDate: fieldsValue.date && getStartTimeStamp(fieldsValue.date[0]),
        endDate: fieldsValue.date && getEndTimeStamp(fieldsValue.date[1]),
      };

      this.setState({
        formValues: values,
      });

      this.getData(values);
    });
  };

  renderForm() {
    const {
      form: { getFieldDecorator },
    } = this.props;
    return (
      <Form onSubmit={this.handleSearch} layout="inline">
        <Row gutter={{ md: 8, lg: 24, xl: 48 }}>
          <Col md={8} sm={24}>
            <FormItem label="名称">
              {getFieldDecorator('name')(<Input placeholder="请输入" />)}
            </FormItem>
          </Col>
          <Col md={8} sm={24}>
            <FormItem label="创建日期">
              {getFieldDecorator('date')(<RangePicker style={{ width: '100%' }} />)}
            </FormItem>
          </Col>
          <Col md={8} sm={24}>
            <Button type="primary" htmlType="submit">
              查询
            </Button>
            <Button style={{ marginLeft: 8 }} onClick={this.handleFormReset}>
              重置
            </Button>
          </Col>
        </Row>
        <div style={{ overflow: 'hidden' }}>
          <div style={{ float: 'left', marginBottom: 24 }}>
            <Button
              icon="plus"
              type="primary"
              onClick={() => router.push({ pathname: '/common/landing-page-management/add' })}
            >
              新增
            </Button>
          </div>
        </div>
      </Form>
    );
  }

  render() {
    const {
      studentManagment: { list },
      loading,
    } = this.props;
    const { selectedRows } = this.state;
    return (
      <PageHeaderWrapper>
        <Card bordered={false}>
          <div className={styles.tableList}>
            <div className={styles.tableListForm}>{this.renderForm()}</div>
            <Table
              selectedRows={selectedRows}
              loading={loading}
              data={list}
              columns={this.columns}
              onSelectRow={this.handleSelectRows}
              onChange={this.handleStandardTableChange}
            />
          </div>
        </Card>
      </PageHeaderWrapper>
    );
  }
}

export default StudentManagement;

上面是一个用Table来做查询列表功能的一个小demo,大致流程如下:

  • 引入Table组件,编写页面jsx;
  • 编写columns数据;
  • 根据自己的需求编写搜索表单的jsx;
  • 处理查询事件,pageChange事件等

可以发现这其实是一个重复的工作,大部分情况下只需要按照固定熟悉来写差不多一样的代码就好了。

现在,我们有了ProTable,再来看看怎么做呢~

import { PlusOutlined } from '@ant-design/icons';
import { Button, Divider } from 'antd';
import React, { Component, Fragment } from 'react';
import router from 'umi/router';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import ProTable from '@ant-design/pro-table';
import { copyString, formatDate, getStartTimeStamp, getEndTimeStamp } from '@/utils/utils';
import { connect } from 'dva';
import { queryRule } from './service';

class StudentManagement extends Component {
  columns = [
    {
      title: '名称',
      dataIndex: 'name',
      ellipsis: true,
    },
    {
      title: '创建日期',
      dataIndex: 'createTime',
      valueType: 'dateRange',
      render: val => val && formatDate(val),
    },
    {
      title: '性别',
      dataIndex: 'sex',
      hideInSearch: true,
    },
    {
      title: '年龄',
      dataIndex: 'age',
      hideInSearch: true,
    },
    {
      title: '学校',
      dataIndex: 'school',
      hideInSearch: true,
    },
    {
      title: '年级',
      dataIndex: 'grade',
      hideInSearch: true,
    },
    {
      title: '操作',
      dataIndex: 'option',
      valueType: 'option',
      render: (_, record) => (
        <Fragment>
          <a
            onClick={() =>
              router.push({
                pathname: '/public-resource/landing-page-management/edit',
                query: {
                  id: record.id,
                },
              })
            }
          >
            编辑
          </a>
          <Divider type="vertical" />
          <a onClick={() => copyString(record.shortUrl)}>复制链接</a>
        </Fragment>
      ),
    },
  ];

  render() {
    return (
      <PageHeaderWrapper>
        <ProTable
          toolBarRender={() => [
            <Button
              type="primary"
              onClick={() => router.push('/public-resource/landing-page-management/add')}
            >
              <PlusOutlined />
              新建
            </Button>,
          ]}
          rowKey="id"
          dateFormatter="number"
          request={params =>
            queryRule({
              ...params,
              current: undefined,
              startDate: params.createTime && getStartTimeStamp(params.createTime[0]),
              endDate: params.createTime && getEndTimeStamp(params.createTime[1]),
            })
          }
          columns={this.columns}
        />
      </PageHeaderWrapper>
    );
  }
}

export default connect(({ studentManagement }) => ({
  studentManagement,
}))(StudentManagement);

写完之后最直观的感受就是代码量足足少了一半!!ProTable将请求数据、处理数据、渲染节点的这个过程封装了起来,使得我们只需要按照他的约定来进行配置即可完成大部分操作。现在:我们流程就变成了:

  • 引入ProTable组件和请求数据的方法并配置好;
  • 按照ProTable的规则维护columns即可。

在新的ProTable中,与以往不同的是,我们需要维护的columns中新增了一个valueType属性,按照约定值,ProTable将根据这个属性来渲染表单项和表头。剩下的Search事件、Reset事件、pageChange事件等等等等...ProTable都会帮你做好。

ProTable大法好,上车保平安。学会用ProTable,从此列表一把梭~

官方文档地址:protable.ant.design/

文章写得比较简略,有问题欢迎在评论区留言~