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

代码流程

相关推荐
多加点辣也没关系14 分钟前
JavaScript|第9章:对象 — 基础
开发语言·javascript·ecmascript
没有了遇见26 分钟前
AI Agent 是什么?—— 一文理解 LLM、Memory、Skills、Tools、MCP、Workflow,Context
android·前端·程序员
后台开发者Ethan1 小时前
setState组件更新
前端·javascript·react
谙忆10241 小时前
大文件上传实战:前端分片、断点续传、秒传与并发控制怎么落地
前端
轻动琴弦2 小时前
VoltTool —— 一站式在线开发者工具平台,69+实用工具免费使用
前端·chrome
就叫飞六吧2 小时前
不同站点iframe嵌套种cookie、localstorege.set() 关系
前端
星栈2 小时前
LiveView 怎么测:从组件测试到端到端,我终于不再只敢手点页面了
前端·前端框架·elixir
蜡台2 小时前
uniapp vue2 转 vue3
前端·javascript·uni-app·vue3·vue2
Web4Browser2 小时前
Headless 浏览器自动化适合哪些任务?巡检、截图和提交动作要分级
运维·前端·自动化
用户059540174462 小时前
把前端内存泄漏排查从2小时压到5分钟,我们把它集成进了CI
前端·css