前言
在中后台项目中会大量使用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;
}