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

代码流程

相关推荐
木易士心7 分钟前
Vue2 和 Vue3 中 watch 用法和原理详解
前端·vue.js
Harlen13 分钟前
Cesium.js基本使用
前端
拿不拿铁1913 分钟前
Webpack 5.x 开发模式启动流程详解
前端
程序猿_极客34 分钟前
【期末网页设计作业】HTML+CSS+JS 旅行社网站、旅游主题设计与实现(附源码)
javascript·css·html·课程设计·期末网页设计
百***359439 分钟前
如何在树莓派部署Nginx并实现无公网ip远程访问内网制作的web网站
前端·tcp/ip·nginx
用户2832096793742 分钟前
为什么我的页面布局总是乱糟糟?可能是浮动和BFC在作怪!
javascript
花果山总钻风1 小时前
Chrome 插件框架 Plasmo 基本使用示例
前端·chrome
资讯第一线1 小时前
《Chrome》 [142.0.7444.60][绿色便携版] 下载
前端·chrome
会篮球的程序猿1 小时前
原生表格文本过长展示问题,参考layui长文本,点击出现文本域
前端·javascript·layui
top_designer1 小时前
Firefly 样式参考:AI 驱动的 UI 资产“无限”生成
前端·人工智能·ui·aigc·ux·设计师