可编辑表格是 ProTable 中呼声比较高的功能,在项目中虽然使用频率不高,但是实现起来难度确不小。所以 ProTable 在完成了架构升级后,就开始着手可编辑表格的开发,现在它终于来了。
🧚🏻♀️ 默认好看的样式
作为了 Ant Design 的衍生作品,我们对 EditableTable 的样式进行了预设,我们很容易就能做出这么好看的可编辑表格。同时我们提供了顶部添加和底部添加两种模式,用于适应不同的场景。我们可以做到默认好看,同时默认好用。
为了改善在狭窄空间内的错误提示,我们重写错误信息的展示方式,使用 Tooltip 类似的方式来展示错误。当然为了防止抖动,我们也重写了Form 的样式使其更加的适合狭窄的输入区域,这样在使用 EditableTable 时并不会产生明显的跳动感,感觉非常顺滑。
👩🏻🦽 默认好用的 API
EditableTable 定义了一套和 Ant Design 相同的 API , 如果你是熟练的 Ant Design 使用者在使用时会感觉到非常的熟悉。为了方便大家使用 EditableTable
修改了 value
和 onChange
,只要放到 Form 中就会像 Input
一样自动绑定数据。
除了 value 和 onChange 我们还提供了 editable 来自定义编辑表格的行为,包括是否支持多行编辑,当前正在编辑行的 key 等,基本可以满足所有的开发需求。
属性 | 描述 | 类型 |
---|---|---|
type | 可编辑表格的类型,单行编辑或者多行编辑 | single | multiple |
editableKeys | 正在编辑的行,受控属性。 默认 key 会使用 rowKey 的配置,如果没有配置会使用 index |
Key[] |
onChange | 行的数据发生改变时触发 | (editableKeys:Key[], editableRows: T[]) => void |
onSave | 保存一行的时候触发,只更新 | (key: Key, row: T,newLine?: newLineConfig) => Promise<boolean> |
onDelete | 删除一行的时候触发 | (key: Key, row: T) => Promise<boolean> |
onCancel | 取消行编辑时触发 | (key: Key, row: T,newLine?: newLineConfig) => Promise<boolean> |
actionRender | 自定义编辑模式的操作栏 | (row: T, config: ActionRenderConfig<T>) => React.ReactNode[] |
🎣 如何使用?
editable 编辑配置
市面上的可编辑表格是非常多的,但是很多使用起来非常麻烦,Table 的表单区域虽然小但是输入控件却不简单,常见的文本,下拉框,数组,日期甚至有时候还会有麻烦的日期区间等,那么 EditableTable 是如何解决这个问题的?
EditableTable 是基于 ProTable 实现的,在 ProTable 中我们是有查询表单这个功能的,通过配置不同的 valueType
就可以生成不同的查询表单,可编辑表格也是使用了同样的 API ,下图是 ProTable 支持的所有日期类的 valueType
。
在这样的能力加持下,EditableTable 的使用变得非常简单,我们可以像 rowSelection 那样使用 editable ,下面是一个简单的例子。
const columns: ProColumns<DataSourceType>[] = [
{
title: '活动名称',
dataIndex: 'title',
formItemProps: {
rules: [
{
required: true,
message: '此项为必填项',
},
],
},
},
{
title: '状态',
key: 'state',
dataIndex: 'state',
valueType: 'select',
valueEnum: {
all: { text: '全部', status: 'Default' },
open: {
text: '未解决',
status: 'Error',
},
closed: {
text: '已解决',
status: 'Success',
},
},
},
{
title: '描述',
dataIndex: 'decs',
valueType: 'text'
},
{
title: '操作',
valueType: 'option',
render: (text, record, _, action) => [
<a
key="editable"
onClick={() => {
action.startEditable?.(record.id);
}}
>
编辑
</a>,
],
},
];
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
const [dataSource, setDataSource] = useState<DataSourceType[]>([]);
<EditableProTable<DataSourceType>
rowKey="id"
headerTitle="可编辑表格"
columns={columns}
value={dataSource}
onChange={setDataSource}
recordCreatorProps={{
position: 'end',
record: { id: (Math.random() * 1000000).toFixed(0) },
}}
editable={{
editableKeys,
onChange: setEditableRowKeys,
}}
/>;
我们可以控制 editableKeys 来修改当前编辑的行,value 来控制当前的数据。以上的代码会生成下面的样式。
action 默认行为
可编辑表格的重点是 action ,这个变量有点像 Form 的 formInstance 实例,你可以调用它的方法来操作可编辑表格的一些行为
,以下是支持的 API 列举:
action.startEditable(rowKey)
开始编辑一行action.cancelEditable(rowKey)
结束编辑一行,相当于取消action.addEditRecord(row)
新增一行,row 相当于默认值,一定要包含 rowKey
recordCreatorProps 新建按钮配置
为了使用,我们预设了一个新建的功能,大多数情况下已经可以满足大部分新建的需求,但是很多时候需求总是千奇百怪。我们也准备了 recordCreatorProps
来控制生成按钮。与 Pro系列组件的API 相同,recordCreatorProps={false}
就可以关掉按钮,同时使用 actionRef.current?.addEditRecord(row)
来控制新建行。
recordCreatorProps
也支持自定义一些样式,position='top'|'end'
可以配置增加在表格头还是表格尾部。record
可以配置新增行的默认数据。以下是一个列举
recordCreatorProps={
// 顶部添加还是末尾添加
position: 'end',
// 不写 key ,会使用 index 当行 id
record: {},
// https://ant.design/components/button-cn/#API
...antButtonProps,
}
renderFormItem 自定义编辑组件
虽然我们很希望默认的 valueType 可以满足所有的需求,但是现实往往不尽如人意。所以我们也提供了 renderFormItem
来自定义编辑输入组件。
renderFormItem
可以理解为在 Form.Item 下面加入一个元素, 伪代码实现是下面这样的:
const dom = renderFormItem();
<Form.Item>
{dom}
</Form.Item>
所以与 Form.Item 相同,我们认为 renderFormItem
返回的组件都是拥有的 value
和 onChange
的,我们接下来将看到用 renderFormItem
将一个简单的 TagList 组件放入可编辑表格中。
没有
value
将会无法注入值,没有onChange
会无法修改行数据
首先我们定义一个 TagList 组件。
const TagList: React.FC<{
value?: {
key: string;
label: string;
}[];
onChange?: (
value: {
key: string;
label: string;
}[],
) => void;
}> = ({ value, onChange }) => {
const ref = useRef<Input | null>(null);
const [newTags, setNewTags] = useState<
{
key: string;
label: string;
}[]
>([]);
const [inputValue, setInputValue] = useState<string>('');
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
};
const handleInputConfirm = () => {
let tempsTags = [...(value || [])];
if (inputValue && tempsTags.filter((tag) => tag.label === inputValue).length === 0) {
tempsTags = [...tempsTags, { key: `new-${tempsTags.length}`, label: inputValue }];
}
onChange?.(tempsTags);
setNewTags([]);
setInputValue('');
};
return (
<Space>
{(value || []).concat(newTags).map((item) => (
<Tag key={item.key}>{item.label}</Tag>
))}
<Input
ref={ref}
type="text"
size="small"
style={{ width: 78 }}
value={inputValue}
onChange={handleInputChange}
onBlur={handleInputConfirm}
onPressEnter={handleInputConfirm}
/>
</Space>
);
};
在列中我们可以这样配置它。
{
title: '标签',
dataIndex: 'labels',
width: '40%',
renderFormItem: () => <TagList />,
render: (_, row) => row?.labels?.map((item) => <Tag key={item.key}>{item.label}</Tag>),
},
转化成的编辑表格效果如下 :
value 和 onChange 会自动注入,我们不需要显式的注入。数据绑定也是由编辑表格自己注入的,我们在 onSave
中可以拿到处理完成的数据。虽然我们可以行内的写出复杂的逻辑甚至网络请求,但是我们仍然推荐拆分组件,这样不仅性能更好,逻辑也可以拆分到另外的地方。
renderFormItem
同时也用来生成查询表单,如果我们需要区分这两种情况,可以使用renderFormItem: (_, { isEditable }) => (isEditable ? <TagList /> : <Input /> )
这样的方式来进行分别渲染。
actionRender 自定义操作栏
可编辑表格默认提供了三大金刚, 保存,删除 和 取消,如果我们要实现复制一行,或者需求只需要的 保存和取消,不需要删除按钮就需要自定义了。可编辑表格提供了 API 来进行自定义,以下会直接展示代码:
复制一行到底部
render: (text, record, _, action) => [
<a
key="editable"
onClick={() => {
action.startEditable?.(record.id);
}}
>
编辑
</a>,
<EditableProTable.RecordCreator
record={{
...record,
id: (Math.random() * 1000000).toFixed(0),
}}
>
<a>复制此行到末尾</a>
</EditableProTable.RecordCreator>,
]
自定义操作栏
const editable = {
actionRender: (row, config) => [
<a
key="save"
onClick={async () => {
const values = (await config?.form?.validateFields()) as DataSourceType;
const hide = message.loading("保存中。。。");
await config?.onSave?.(config.recordKey, { ...row, ...values });
hide();
}}
>
保存
</a>,
<a
key="save"
onClick={async () => {
await config?.onCancel?.(config.recordKey, row);
}}
>
取消
</a>,
],
};
🔐 何时应该使用
ProComponents 的测试覆盖了达到了 97%,虽然离 antd 的 100% 还有很长的距离,但是已经可以保证不会因为变更而出现恼人的不兼容问题,同时在内部已经在数个项目中使用。如果你仍然保有疑虑,可以在我们的官网体验。
如果使用中碰到了任何问题,都可以提 issue,或者直接进行 PR。也许你的想法和意见可以帮助到更多的人。
🔔 其他事情
ProComponents 同时还提供了其他的组件。
- ProLayout 解决布局的问题,提供开箱即用的菜单和面包屑功能
- ProTable 解决表格问题,抽象网络请求和表格格式化
- ProForm 解决表单问题,预设常见布局和行为
- ProCard 提供卡片切分以及栅格布局能力
- ProDescriptions 提供与 table 使用同等配置的能力
- ProSkeleton 页面级别的骨架屏
其中的 ProTable ,ProForm ,ProDescriptions 使用了同样的架构,以 valueType 为核心的场景化的格式工具,如果你喜欢可编辑表格,那么你也会喜欢同样的可编辑定义列表,同样的还有只读的表单。
最后如果有什么问题,欢迎来提 issue 和 吐槽讨论。 ProComponents 还是个新生的组件库,如果有 star 和 PR 那就更加完美。