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

代码流程

相关推荐
HEX9CF18 分钟前
【CTF Web】Pikachu xss之href输出 Writeup(GET请求+反射型XSS+javascript:伪协议绕过)
开发语言·前端·javascript·安全·网络安全·ecmascript·xss
凌云行者30 分钟前
使用rust写一个Web服务器——单线程版本
服务器·前端·rust
华农第一蒟蒻1 小时前
Java中JWT(JSON Web Token)的运用
java·前端·spring boot·json·token
积水成江1 小时前
关于Generator,async 和 await的介绍
前端·javascript·vue.js
Z3r4y1 小时前
【Web】portswigger 服务端原型污染 labs 全解
javascript·web安全·nodejs·原型链污染·wp·portswigger
___Dream1 小时前
【黑马软件测试三】web功能测试、抓包
前端·功能测试
金灰1 小时前
CSS3练习--电商web
前端·css·css3
人生の三重奏1 小时前
前端——js补充
开发语言·前端·javascript
Tandy12356_1 小时前
js逆向——webpack实战案例(一)
前端·javascript·安全·webpack
TonyH20021 小时前
webpack 4 的 30 个步骤构建 react 开发环境
前端·css·react.js·webpack·postcss·打包