Skip to content

Commit

Permalink
docs: add 可编辑表格发布
Browse files Browse the repository at this point in the history
  • Loading branch information
chenshuai2144 committed Feb 23, 2021
1 parent 2e004bf commit 61f7fcd
Showing 1 changed file with 319 additions and 0 deletions.
319 changes: 319 additions & 0 deletions docs/editableTable.md
Original file line number Diff line number Diff line change
@@ -0,0 1,319 @@
---
title: 🥳 可能是最好的可编辑表格发布啦
order: 5
group:
path: /
nav:
title: 文档
order: 1
path: /docs
---

# 🥳 可能是最好的可编辑表格发布啦

可编辑表格是 ProTable 中呼声比较高的功能,在项目中虽然使用频率不高,但是实现起来难度确不小。所以 ProTable 在完成了架构升级后,就开始着手可编辑表格的开发,现在它终于来了。

## 🧚🏻‍♀️ 默认好看的样式

作为了 Ant Design 的衍生作品,我们对 EditableTable  的样式进行了预设,我们很容易就能做出这么好看的可编辑表格。同时我们提供了顶部添加和底部添加两种模式,用于适应不同的场景。我们可以做到默认好看,同时默认好用。

![image.png](https://cdn.nlark.com/yuque/0/2020/png/84868/1606962433432-6d369376-4c7d-4e67-88a1-e9190ebf0434.png#align=left&display=inline&height=171&margin=[object Object]&name=image.png&originHeight=342&originWidth=1298&size=40455&status=done&style=shadow&width=649)

为了改善在狭窄空间内的错误提示,我们重写错误信息的展示方式,使用 Tooltip 类似的方式来展示错误。当然为了防止抖动,我们也重写了 Form 的样式使其更加的适合狭窄的输入区域,这样在使用 EditableTable  时并不会产生明显的跳动感,感觉非常顺滑。 ![image.png](https://cdn.nlark.com/yuque/0/2020/png/84868/1607270367385-5df9ff51-fb57-434d-a0b0-91a6bafb9210.png#align=left&display=inline&height=141&margin=[object Object]&name=image.png&originHeight=423&originWidth=2222&size=64902&status=done&style=none&width=740.6666666666666)

## 👩🏻‍🦽 默认好用的 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`

![image.png](https://cdn.nlark.com/yuque/0/2020/png/84868/1608191542231-c4dafeba-ef9f-4a62-b44b-f59fa006826c.png#align=left&display=inline&height=552&margin=[object Object]&name=image.png&originHeight=1103&originWidth=3803&size=187111&status=done&style=shadow&width=1902)

在这样的能力加持下,EditableTable  的使用变得非常简单,我们可以像 rowSelection 那样使用 editable ,下面是一个简单的例子。

```typescript | pure
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 来控制当前的数据。以上的代码会生成下面的样式。

![image.png](https://cdn.nlark.com/yuque/0/2020/png/84868/1608204294399-b102f885-cc65-4db7-b338-74555e11777d.png#align=left&display=inline&height=195&margin=[object Object]&name=image.png&originHeight=586&originWidth=3779&size=92305&status=done&style=shadow&width=1259.6666666666667)

### 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` 可以配置新增行的默认数据。以下是一个列举

```typescript | pure
recordCreatorProps = {
// 顶部添加还是末尾添加
position: 'end',
// 不写 key ,会使用 index 当行 id
record: {},
// https://ant.design/components/button-cn/#API
...antButtonProps,
};
```

### renderFormItem 自定义编辑组件

虽然我们很希望默认的 valueType 可以满足所有的需求,但是现实往往不尽如人意。所以我们也提供了 `renderFormItem` 来自定义编辑输入组件。

`renderFormItem` 可以理解为在 Form.Item 下面加入一个元素, 伪代码实现是下面这样的:

```typescript | pure
const dom = renderFormItem();

<Form.Item>{dom}</Form.Item>;
```

所以与 Form.Item 相同,我们认为 `renderFormItem` 返回的组件都是拥有的 `value``onChange` 的,我们接下来将看到用 `renderFormItem` 将一个简单的 TagList 组件放入可编辑表格中。

> 没有 `value` 将会无法注入值,没有 `onChange` 会无法修改行数据
首先我们定义一个 TagList 组件。

```typescript | pure
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>
);
};
```

在列中我们可以这样配置它。

```typescript | pure
{
title: '标签',
dataIndex: 'labels',
width: '40%',
renderFormItem: () => <TagList />,
render: (_, row) => row?.labels?.map((item) => <Tag key={item.key}>{item.label}</Tag>),
},
```

转化成的编辑表格效果如下 :

![image.png](https://cdn.nlark.com/yuque/0/2020/png/84868/1608213872536-a8c50750-6fc3-489b-af34-ff5980a33ab2.png#align=left&display=inline&height=208&margin=[object Object]&name=image.png&originHeight=623&originWidth=3768&size=103416&status=done&style=shadow&width=1256)

value 和 onChange 会自动注入,我们不需要显式的注入。数据绑定也是由编辑表格自己注入的,我们在 `onSave` 中可以拿到处理完成的数据。虽然我们可以行内的写出复杂的逻辑甚至网络请求,但是我们仍然推荐拆分组件,这样不仅性能更好,逻辑也可以拆分到另外的地方。

> `renderFormItem` 同时也用来生成查询表单,如果我们需要区分这两种情况,可以使用 `renderFormItem: (_, { isEditable }) => (isEditable ? <TagList /> : <Input /> )` 这样的方式来进行分别渲染。
### actionRender 自定义操作栏

可编辑表格默认提供了三大金刚, 保存,删除 和 取消,如果我们要实现复制一行,或者需求只需要的 保存和取消,不需要删除按钮就需要自定义了。可编辑表格提供了 API 来进行自定义,以下会直接展示代码:

#### 复制一行到底部

```typescript | pure
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>,
];
```

#### 自定义操作栏

```typescript | pure
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% 还有很长的距离,但是已经可以保证不会因为变更而出现恼人的不兼容问题,同时在内部已经在数个项目中使用。如果你仍然保有疑虑,可以在我们的[官网](https://procomponents.ant.design/)体验。 ![image.png](https://cdn.nlark.com/yuque/0/2020/png/84868/1608217583485-ef259203-5d85-46a3-8ef3-f4d015338617.png#align=left&display=inline&height=162&margin=[object Object]&name=image.png&originHeight=485&originWidth=2284&size=110333&status=done&style=none&width=761.3333333333334) 如果使用中碰到了任何问题,都可以提 issue,或者直接进行 PR。也许你的想法和意见可以帮助到更多的人。

## 🔔 其他事情

ProComponents 同时还提供了其他的组件。

- [ProLayout](https://procomponents.ant.design/components/layout) 解决布局的问题,提供开箱即用的菜单和面包屑功能
- [ProTable](https://procomponents.ant.design/components/table) 解决表格问题,抽象网络请求和表格格式化
- [ProForm](https://procomponents.ant.design/components/form) 解决表单问题,预设常见布局和行为
- [ProCard](https://procomponents.ant.design/components/card) 提供卡片切分以及栅格布局能力
- [ProDescriptions](https://procomponents.ant.design/components/descriptions) 提供与 table 使用同等配置的能力
- [ProSkeleton](https://procomponents.ant.design/components/skeleton) 页面级别的骨架屏

其中的 ProTable ,ProForm ,ProDescriptions 使用了同样的架构,以 valueType 为核心的场景化的格式工具,如果你喜欢可编辑表格,那么你也会喜欢同样的可编辑定义列表,同样的还有只读的表单。

最后如果有什么问题,欢迎来提[ issue](https://github.com/ant-design/pro-components)[吐槽讨论](https://github.com/ant-design/pro-components/discussions)。 ProComponents 还是个新生的组件库,如果有 star 和 PR 那就更加完美。

0 comments on commit 61f7fcd

Please sign in to comment.