实现一个可编辑校验的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;
}

代码流程

相关推荐
RaidenLiu11 分钟前
告别陷阱:精通Flutter Signals的生命周期、高级API与调试之道
前端·flutter·前端框架
非凡ghost11 分钟前
HWiNFO(专业系统信息检测工具)
前端·javascript·后端
非凡ghost13 分钟前
FireAlpaca(免费数字绘图软件)
前端·javascript·后端
非凡ghost20 分钟前
Sucrose Wallpaper Engine(动态壁纸管理工具)
前端·javascript·后端
拉不动的猪21 分钟前
为什么不建议项目里用延时器作为规定时间内的业务操作
前端·javascript·vue.js
该用户已不存在28 分钟前
Gemini CLI 扩展,把Nano Banana 搬到终端
前端·后端·ai编程
地方地方30 分钟前
前端踩坑记:解决图片与 Div 换行间隙的隐藏元凶
前端·javascript
炒米233332 分钟前
【Array】数组的方法
javascript
小猫由里香35 分钟前
小程序打开文件(文件流、地址链接)封装
前端
Tzarevich38 分钟前
使用n8n工作流自动化生成每日科技新闻速览:告别信息过载,拥抱智能阅读
前端