前置工作
使用 pnpm i antd @ant-design/icons
安装 antd 和它提供的图标库来作为我们项目的 UI 库。
编码流程
1.修改 Table 组件
我们参照 antd 的官方文档,定义一个 columns 来描述每一列。
tsx
//App.tsx
const columns = [
{
title: '用户ID',
dataIndex: 'userId',
key: 'userId',
},
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: '所在地',
dataIndex: 'city',
key: 'city',
},
{
title: '角色名称',
dataIndex: 'role',
key: 'role',
},
{
title: '操作',
dataIndex: 'operation',
key: 'operation',
},
]
//...
return (
//...
<Table columns={columns} dataSource={filterData} />
)
现在 Table 组件已经能够正常显示数据了,下一步我们将原有的操作的按钮和编辑模式加上,查阅文档我们可以发现 Column 提供了一个 render 的接口,用来定义每一列的渲染逻辑,入参形式为(当前单元格的值,当前行数据,行索引) => {}
,我们根据之前的代码逻辑来改写对应的列定义。
tsx
//App.tsx
const columns: TableProps<User>["columns"] = [
{
title: "姓名",
dataIndex: "name",
key: "name",
render: (_, record) => {
return (
<>
{editMap[record.userId] ? (
<input
ref={(node) => {
const map = getMap();
map.set(record.userId, node as HTMLInputElement);
return () => {
map.delete(record.userId);
};
}}
value={editMap[record.userId].name}
onChange={(e) => handleChange(e, record.userId)}
/>
) : (
<span>{record.name}</span>
)}
</>
);
},
},
//...
{
title: "操作",
dataIndex: "operation",
key: "operation",
render: (_, record) => {
return (
<>
{editMap[record.userId] ? (
<button onClick={() => handleConfirmEdit(record.userId)}>
保存
</button>
) : (
<button onClick={() => handleEdit(record.userId)}>修改</button>
)}
<button onClick={() => handleDelete(record.userId)}>删除</button>
</>
);
},
},
];
2. 修改 Form 组件
使用 Form 组件的时候,如果是用过 Vue 的同学可能会感觉到有点别扭。一般在 Vue 中我们会定义一个 formData
的响应式对象来收集数据,然后在控件上使用 v-model
来控制控件的值。而查看 Antd 的 Form 组件示例代码,会发现它完全没有在控件上传入 value
或是 onChange
这样的字段,Form 组件内集中管理了表单控件的状态,并且向外暴露 useForm
hook 让我们可以获取状态或者操作它们。
tsx
// App.tsx
function UserForm({ onAddUser }: { onAddUser: (user: UserFormData) => void }) {
const [form] = Form.useForm();
const handleReset = () => {
form.resetFields();
};
const handleSubmit = async () => {
// 触发校验
await form.validateFields()
// 获取表单的值
const formData = form.getFieldsValue();
onAddUser({
...formData,
age: Number(formData.age),
});
// 添加之后 reset
handleReset()
};
return (
<>
<Form
form={form}
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
style={{ maxWidth: 600 }}
initialValues={defaultFormData}
autoComplete="off"
>
<Form.Item<UserFormData>
label="姓名"
name="name"
rules={[{ required: true, message: "请输入姓名" }]}
>
<Input />
</Form.Item>
<Form.Item<UserFormData>
label="年龄"
name="age"
rules={[{ required: true, message: "请输入年龄" }]}
>
<Input />
</Form.Item>
<Form.Item<UserFormData>
label="所在地"
name="city"
rules={[{ required: true }]}
>
<Select>
<Select.Option value={"深圳"}>深圳</Select.Option>
<Select.Option value={"广州"}>广州</Select.Option>
</Select>
</Form.Item>
<Form.Item<UserFormData>
label="角色"
name="role"
rules={[{ required: true }]}
>
<Select>
<Select.Option value={"销售"}>销售</Select.Option>
<Select.Option value={"销售经理"}>销售经理</Select.Option>
</Select>
</Form.Item>
<Form.Item label={null}>
<button type="button" onClick={handleSubmit}>
添加
</button>
<button type="button" onClick={handleReset}>
重置
</button>
</Form.Item>
</Form>
</>
);
}
验证一下代码功能,发现和我们预期的一样。handleSubmit
其实还能够简化, Form 组件会监听 onSubmit
,在校验成功后触发一个名为 onFinished
事件。
tsx
// App.tsx
const handleSubmit = (values: UserFormData) => {
onAddUser({
...values,
age: Number(values.age),
});
// 添加之后 reset
handleReset()
};
//...
<Form
//...
onFinish={handleSubmit}
>
//...
<Form.Item label={null}>
// 修改 type 为 submit,触发表单的 submit 事件
<button type="submit">
添加
</button>
<button type="button" onClick={handleReset}>
重置
</button>
</Form.Item>
</Form>
小结
我们将原生 HTML 标签替换为了常用 UI 库的组件,并且发现了一些它设计理念上与 Vue 实践中的差异,接下来我们将引入 React Router,来将表单与列表分开到不同的页面。
