实现一个可编辑校验的Table组件

前言

在中后台项目中会大量使用table列表,列表数据的数据源通常由用户通过新增操作手动添加。添加交互一般为传统的from表单形式,但是有时产品会希望能在当前table中直接编辑。

社区里成熟的组件库如antd等当然可以满足编辑需求,但是,提交时的数据检验逻辑却还需要用户手动维护。在组件使用场景过多,往往会造成代码冗余,此时知道如何从零实现一个自定义的table组件就十分重要了。

本文从实际需求出发,简要分析了组件的结构及实现原理,完整示例代码【请点击这里】

组件解析

组件结构

由需求分析可知,EditTable组件有两个基本状态:

  • 展示态

    • 与一般Table组件相同
  • 编辑态

    • 单条数据的编辑态与Form组件的编辑相同

由上可知:EditTable组件可以简单看作是一般Table组件与Form组件的结合。

如下图所示:

table组件渲染时,在FormStore中将Cell作为单独的表单项注册。与普通form不同的是,Table组件为列表展示,因此,注册的Cell也按照列表下标进行分组,便于校验时精确操作该行的Cell。

基础用法

在代码实现前,我们可以先确定一下组件的基础用法:

javascript 复制代码
import React, { useState } from 'react';
import { Table, IColumn } from '@ostore/ui';

function App() {
  const [dataSource, setDataSource] = useState([
    {
      name: 'jack',
      age: 18
    }
  ]);

  const columns:IColumn[] = [
    {
      title: '姓名',
      width: '25%',
      dataIndex: 'name',
      min: 2,
      max: 10,
      required: true,
      message: '姓名长度为2到10个字符',
      cell: (value) => value,
      editCell: (value, index, record) => (
        <input value={value} onChange={(e) => tableChange(e.target.value, 'name', index, record)} />
      )
    },
    {
      title: '年龄',
      width: '25%',
      dataIndex: 'age',
      validator: async (value, name, index, record) => {
        return new Promise(resolve => {
          resolve(true);
        });
      },
      cell: (value) => value,
      editCell: (value, index, record) => (
        <input value={value} onChange={(e) => tableChange(e.target.value, 'age', index, record)} />
      )
    },
    {
      title: '操作',
      width: '25%',
      dataIndex: 'handler',
      cell: (value, index, record) => (
        <>
          <button onClick={() => handler(record)}>{record.isEdit ? '保存' : '编辑'}</button>
          <button onClick={() => remove(record)}>删除</button>
        </>
      ),
    },
  ]

  const field = Table.useField();
  
  return (
    <div style={{ margin: 20 }}>
      <Table 
        field={field}
        dataSource={dataSource}
        columns={columns}
        editRow={record => record.isEdit}
      />
    </div>
  )
}

export default App

属性定义

ClassUseField

field中应包含基础的值检验、值获取、值设置,以及错误信息获取,错误信息设置等能力。

typescript 复制代码
class ClassUseField {
  resetFieldValues: () => void;
  setFieldValue: (index:number, name: string, value: any) => void;
  setFieldValues: (index:number, values:any) => void;
  getFieldValue: (index:number, name: string) => any;
  getFieldValues: (index?:number) => any;

  setFieldError: (index:number, name: string, value: any) => void;
  setFieldErrors: (index:number, errors: any) => void;
  getFieldError: (index:number, name?: string) => any;
  getFieldErrors: (index?: number) => any;
 
  validator: (index?:number, name?: string) => any; // 校验器
  registerField: (index, config: any) => any; // 注册cell

  // 其他扩展属性
  ...
}

EditTable Props

EditTable组件的UI展示能力与一般table相同,因此可定义以下基础属性:

typescript 复制代码
type EditRow = (item: any) => boolean;

interface IEditTableProps {
  // 基础属性
  columns: IColumn[]; // 表头
  dataSource: any[]; // 数据源
  field?: ClassUseField; // 可编辑时需传入,覆盖默认field
  editRow?: EditRow;  // 返回可以编辑的行
  // 其他扩展属性
  ...
}

Column Props

因为EditTable中结合form的编辑校验能力,因此Column中除了常规的属性外,还多出了校验相关的属性,如下所示:

typescript 复制代码
interface IRules {
  min?: number; // 最小长度
  max?: number; // 最大长度
  required?: boolean; // 是否必填
}

type TRulesKey = keyof IRules;
type IRuleProps = {
  [P in TRulesKey]?: { 
    [K in P]: IRules[K];
  } & { message?: ReactNode }
}[TRulesKey];

type TValidatorResult = boolean | ReactNode | Promise<boolean|ReactNode>
interface IColumn extends IRules {
  dataIndex: string;
  title?: ReactNode;
  width?: number | string;
  // 校验失败提示消息
  message?: ReactNode;
  // 校验器是否防抖处理
  debounce?: number; 
  // 校验器
  validator?: (value: any, name: string, record: any, index: number) => TValidatorResult; // 校验器
  // 校验规则
  rules?: IRuleProps[]; // rules存在时,column中继承的IRules属性会失效
  // 默认渲染函数
  cell?: (value: string|any[], index: number, record: any) => ReactNode;
  // 编辑态渲染函数
  editCell?: (value: string|undefined|any[], index: number, record: any) => ReactNode;
  // 其他扩展属性
}

Row Props

Row组件循环columns,渲染每一个单元格,接收Table组件传递属性,下发给Cell。

css 复制代码
interface IRowProps {
  columns: IColumn[];
  editRow: EditRow | undefined;
  record: any;
  index: number;
  field: ClassUseField;
}

Cell Props

css 复制代码
interface ICellProps {
  column: IColumn;
  index: number;
  editRow: EditRow | undefined;
  record: any;
  field: ClassUseField;
}

代码流程

相关推荐
王解8 分钟前
Vue CLI 脚手架创建项目流程详解 (2)
前端·javascript·vue.js
刘大浪11 分钟前
vue.js滑动到顶便锁定位置
前端·javascript·vue.js
小金刚®18 分钟前
构建简洁之美:我的第一个前端页面
前端
ordinary901 小时前
指令-v-for的key
前端·javascript·vue.js
rpa_top1 小时前
RPA 助力电商:自动化商品信息上传,节省人力资源 —— 以影刀 RPA 为例【rpa.top】
大数据·前端·人工智能·自动化·rpa
新时代农民工--小明1 小时前
前端自动化部署更新,自动化打包部署
运维·前端·自动化
前端Hardy1 小时前
HTML&CSS:酷炫的3D开关控件
前端·javascript·css·3d·html
小马超会养兔子1 小时前
如何写一个数字老虎机滚轮
开发语言·前端·javascript·vue
Web打印1 小时前
web打印插件 HttpPrinter 使用半年评测
javascript·json·firefox·jquery·html5
m0_672449602 小时前
前端(组件传参案例)
前端