前言

项目中需要实现一个选中节点修改其连接桩信息的功能,考虑后选择Table组件实现,即可以展示数据,antd官网也有如何实现可编辑行的示例,虽然看起来不复杂,但刚开始写的时候对于Table行以及Form.Item的切换以及数据流通不是特别清晰,下文主要整理如何用Ant Design实现可以编辑的Table组件,以及梳理清楚实现改功能的一些细节

功能实现

主要组件

这里主要用了antdTable组件,下面尽量讲解各部分含义,不详尽处参见官方文档

这里主要关注formEditableCelldatamergedColumns

  • form,表单实例
  • EditableCell,由于要实现的功能是点击编辑按钮,表单变为可编辑的状态,所以该组件对应表单编辑时的状态
  • data,表单源数据
  • mergedColumns,表格列的配置描述
<Form form={form}>
    <Table
        components={{
            body: {
                cell: EditableCell,
            },
        }}
        dataSource={data}
        columns={mergedColumns}
        />
</Form>

数据数组

根据选择节点信息解析生成

这里的key同样是每一行的key,用作唯一标识,同时也是判断是否是当前行在编辑

//表格数据格式
interface Item {
    key: string;
    name: string;
    dir: string;
    type: string;
}
//表单数据
const [data, setData] = useState<Item[]>([]);
useEffect(() => {
    if (selectNode === null) return;
    const originData: Item[] = [];
    //将ports数据转化为table原始数据
    for (const port of selectNode.getPorts()) {
        //...
        originData.push({
            key: port.id as string,
            name: port.attrs?.text.text as string,
            dir,
            type,
        });
    }
    setData(originData);
}, [selectNode]);

表格列的配置描述

columns生成,解析columns中数据,供EditableCell使用

  const columns = [
    {
      title: '端口名称',
      dataIndex: 'name',
      width: '30%',
      editable: true,
    },
    //...
    {
      title: '操作',
      dataIndex: 'operation',
      render: (_: any, record: Item) => {
        const editable = isEditing(record);
        return editable ? (...) : (...)
      },
    },
  ];
  const mergedColumns = columns.map(col => {
    if (!col.editable) {
      return col;
    }
    return {
      ...col,
      onCell: (record: Item) => ({
        record,
        dataType: col.dataIndex,
        dataIndex: col.dataIndex,
        title: col.title,
        editing: isEditing(record),
      }),
    };
  });

mergedColumnsreturn出对象中的onCell,将数据传递给了table中的cell,这里的dataType等字段均可以自定义,以支持cell组件中的判断逻辑

(注意这里col.editabletrue才会返回onCell中的数据,即对于可编辑单元格才额外返回需要的数据)

components={{
  body: {
    cell: EditableCell,
  },
}}

可编辑单元格

数据格式如下,来源于mergedColumns

//可编辑单元数据格式
interface EditableCellProps extends React.HTMLAttributes<HTMLElement> {
    //是否正在编辑
    editing: boolean;
    //区分Form.Item 'name' | 'dir' | 'type'
    dataIndex: string;
    //Form字段标题
    title: string;
    //数据类型,用于区分编辑状态是input还是select等
    dataType: string;
    //记录正在编辑的数据
    record: Item;
    //索引
    index: number;
    children: React.ReactNode;
}

可编辑单元格组件如下,根据editing变量选择展示Form.Item还是Table自身行数据

  //可编辑单元格
  const EditableCell: React.FC<EditableCellProps> = ({
    editing,
    dataIndex,
    title,
    dataType,
    children,
    ...restProps
  }) => {
    const selectDir = <Select options={nodeType.current === 'func' ? funcDirOptions : moduleDirOptions} />;
    const selectType = <Select options={typeOptions} />;

    //编辑单元格
    const editNode = () => {
      switch (dataType) {
        case 'name':
          return <Input />;
        case 'dir':
          return selectDir;
        case 'type':
          return selectType;
      }
    };

    return (
      <td {...restProps}>
        {editing ? (
          <Form.Item name={dataIndex}>
            {editNode()}
          </Form.Item>
        ) : (
          children
        )}
      </td>
    );
  };

这里分别采用了两种输入,<Input /><Select />,关于组件中的value是如何被获取到的,下面是Form.Item的官方解释

被设置了 name 属性的 Form.Item 包装的控件,表单控件会自动添加 value(或 valuePropName 指定的其他属性) onChange(或 trigger 指定的其他属性),数据同步将被 Form 接管,这会导致以下结果:

  1. 不再需要也不应该onChange 来做数据收集同步(你可以使用 Form 的 onValuesChange),但还是可以继续监听 onChange 事件。
  2. 你不能用控件的 valuedefaultValue 等属性来设置表单域的值,默认值可以用 Form 里的 initialValues 来设置。注意 initialValues 不能被 setState 动态更新,你需要用 setFieldsValue 来更新。
  3. 你不应该用 setState,可以使用 form.setFieldsValue 来动态改变表单值。

获取table中行信息

获取table中行信息是通过render函数获取,关于render函数的api说明如下:

生成复杂数据的渲染函数,参数分别为当前行的值,当前行数据,行索引

function(text, record, index) {}

由于这里只需要用到record参数,所以_:any标识占位,这样就可以正确获取到位置第二的record参数

const columns = [
    //...
    {
        title: '操作',
        dataIndex: 'operation',
        render: (_: any, record: Item) => {
            const editable = isEditing(record);
            return editable ? (
                <Typography.Link onClick={() => save(record.key)}>
                    保存
                </Typography.Link>
            ) : (
                <Typography.Link disabled={editingKey !== ''} onClick={() => edit(record)}>
                    编辑
                </Typography.Link>
            );
        },
    },
]

数据更新流程

如上面所说,我们需要调用setFieldsValue才能改变表单数据,所以我们的行在切换成表单状态时需要根据该行信息预填表单,即调用如下edit函数;当编辑完毕后,调用save函数,获取表单行对应信息。

所以总结一下编辑的流程:

  • 点击编辑按钮:
    • 将正在编辑的key值设为当前行,该行由只展示信息改为展示可编辑表单
    • 调用setFieldsValue,将table中该行原有信息预填入form
  • 编辑信息
  • 点击保存按钮:
    • 验证表单数据,并获取到该行已修改的form数据
    • 找到table组件修改行,根据表单新数据更新table组件数据

即:根据table中数据预填form,又根据form的修改更新table

//开始编辑(预填表单数据)
const edit = (record: Partial<Item> & { key: React.Key }) => {
    form.setFieldsValue({ ...record });
    setEditingKey(record.key);
};
//保存数据
const save = async (key: React.Key) => {
    try {
        //验证表单,获取表单行数据
        const row = (await form.validateFields()) as Item;
        //拷贝更改前data数组
        const newData = [...data];
        //根据传入的key值找到要编辑的数据
        const index = newData.findIndex(item => key === item.key);
        //如果找到对应行数据(index > -1)
        if (index > -1) {
            const item = newData[index];
            //修改对应位置数据 (item为原始数据,row为更新后数据,使用...语法合并)
            newData.splice(index, 1, {
                ...item,
                ...row,
            });
            //更新data数据
            setData(newData);
            //清空editingKey
            setEditingKey('');
            messageApi.success('修改成功!');
        }
    }catch(){}
}

小结

到此已大体实现了编辑节点port信息的table组件,关于修改的数据同步图渲染或后端请求的代码这里没有展示,旨在关注antd如何实现可以edittable组件以及tableform两个组件混合使用时,数据是如何流通,如何更新的


0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用*标注