如何实现Ant Design动态表单字段的上下移动排序功能
Ant Design动态表单实现行上下排序功能
问题描述
我开发了一个支持多字段添加的Ant Design动态表单,已经在表单行中添加了Up和Down按钮,但尚未实现changeOrder函数。需求是通过点击按钮实现对应表单行的上下移动(例如将sight 3移至sight 1和sight 2之间),从而调整表单字段的排序顺序,请问该如何实现这一功能?
原始代码
import React from "react"; import "./index.css"; import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons"; import { Button, Form, Input, Select, Space } from "antd"; const { Option } = Select; const areas = [ { label: "Beijing", value: "Beijing" }, { label: "Shanghai", value: "Shanghai" } ]; const sights = { Beijing: ["Tiananmen", "Great Wall"], Shanghai: ["Oriental Pearl", "The Bund"] }; type SightsKeys = keyof typeof sights; const App: React.FC = () => { const [form] = Form.useForm(); const onFinish = (values: any) => { console.log("Received values of form:", values); }; const handleChange = () => { form.setFieldsValue({ sights: [] }); }; return ( <Form form={form} name="dynamic_form_complex" onFinish={onFinish} style={{ maxWidth: 600 }} autoComplete="off" > <Form.Item name="area" label="Area" rules={[{ required: true, message: "Missing area" }]} > <Select options={areas} onChange={handleChange} /> </Form.Item> <Form.List name="sights"> {(fields, { add, remove }) => ( <> {fields.map((field, index) => ( <Space key={field.key} align="baseline"> <Form.Item noStyle shouldUpdate={(prevValues, curValues) => prevValues.area !== curValues.area || prevValues.sights !== curValues.sights } > {() => ( <Form.Item {...field} label={`Sight ${field.name + 1}`} name={[field.name, "sight"]} rules={[{ required: true, message: "Missing sight" }]} > <Select disabled={!form.getFieldValue("area")} style={{ width: 130 }} > {( sights[form.getFieldValue("area") as SightsKeys] || [] ).map((item) => ( <Option key={item} value={item}> {item} </Option> ))} </Select> </Form.Item> )} </Form.Item> <Form.Item noStyle shouldUpdate={(prev, cur) => prev?.sights[field?.name]?.sight !== cur?.sights[field?.name]?.sight } > {() => { const disabled = !form.getFieldValue([ "sights", field.name, "sight" ]); return ( <Form.Item {...field} label="Price" name={[field.name, "price"]} rules={[{ required: true, message: "Missing price" }]} > <Input disabled={disabled} /> </Form.Item> ); }} </Form.Item> <MinusCircleOutlined onClick={() => remove(field.name)} /> <Button style={{ marginTop: "30px" }} onClick={() => changeOrder(index, "Up")} block > Up </Button> <Button style={{ marginTop: "30px" }} onClick={() => changeOrder(index, "Down")} block > Down </Button> </Space> ))} <Form.Item> <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />} > Add sights </Button> </Form.Item> </> )} </Form.List> <Form.Item> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> ); }; export default App;
解决方案
实现思路
核心是操作Form.List绑定的sights数组数据:获取当前数组副本,交换目标行与相邻行的位置,再通过表单API更新数据,Ant Design Form会自动重新渲染列表完成排序。同时需要处理边界情况,避免无效操作。
修改后的完整代码
import React from "react"; import "./index.css"; import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons"; import { Button, Form, Input, Select, Space } from "antd"; const { Option } = Select; const areas = [ { label: "Beijing", value: "Beijing" }, { label: "Shanghai", value: "Shanghai" } ]; const sights = { Beijing: ["Tiananmen", "Great Wall"], Shanghai: ["Oriental Pearl", "The Bund"] }; type SightsKeys = keyof typeof sights; const App: React.FC = () => { const [form] = Form.useForm(); const onFinish = (values: any) => { console.log("Received values of form:", values); }; const handleChange = () => { form.setFieldsValue({ sights: [] }); }; // 实现changeOrder函数 const changeOrder = (index: number, direction: "Up" | "Down") => { const currentSights = form.getFieldValue("sights") || []; const newSights = [...currentSights]; let targetIndex; if (direction === "Up") { // 第一行不能上移 if (index === 0) return; targetIndex = index - 1; } else { // 最后一行不能下移 if (index === newSights.length - 1) return; targetIndex = index + 1; } // 交换位置 [newSights[index], newSights[targetIndex]] = [newSights[targetIndex], newSights[index]]; // 更新表单值 form.setFieldsValue({ sights: newSights }); }; return ( <Form form={form} name="dynamic_form_complex" onFinish={onFinish} style={{ maxWidth: 600 }} autoComplete="off" > <Form.Item name="area" label="Area" rules={[{ required: true, message: "Missing area" }]} > <Select options={areas} onChange={handleChange} /> </Form.Item> <Form.List name="sights"> {(fields, { add, remove }) => ( <> {fields.map((field, index) => ( <Space key={field.key} align="baseline"> <Form.Item noStyle shouldUpdate={(prevValues, curValues) => prevValues.area !== curValues.area || prevValues.sights !== curValues.sights } > {() => ( <Form.Item {...field} label={`Sight ${field.name + 1}`} name={[field.name, "sight"]} rules={[{ required: true, message: "Missing sight" }]} > <Select disabled={!form.getFieldValue("area")} style={{ width: 130 }} > {( sights[form.getFieldValue("area") as SightsKeys] || [] ).map((item) => ( <Option key={item} value={item}> {item} </Option> ))} </Select> </Form.Item> )} </Form.Item> <Form.Item noStyle shouldUpdate={(prev, cur) => prev?.sights[field?.name]?.sight !== cur?.sights[field?.name]?.sight } > {() => { const disabled = !form.getFieldValue([ "sights", field.name, "sight" ]); return ( <Form.Item {...field} label="Price" name={[field.name, "price"]} rules={[{ required: true, message: "Missing price" }]} > <Input disabled={disabled} /> </Form.Item> ); }} </Form.Item> <MinusCircleOutlined onClick={() => remove(field.name)} /> <Button style={{ marginTop: "30px" }} onClick={() => changeOrder(index, "Up")} disabled={index === 0} block > Up </Button> <Button style={{ marginTop: "30px" }} onClick={() => changeOrder(index, "Down")} disabled={index === fields.length - 1} block > Down </Button> </Space> ))} <Form.Item> <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />} > Add sights </Button> </Form.Item> </> )} </Form.List> <Form.Item> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> ); }; export default App;
关键说明
changeOrder函数逻辑:- 获取当前
sights数组的副本(避免直接修改原数据) - 根据方向判断操作是否合法(比如Up时索引不能为0,Down时不能是最后一个元素)
- 交换目标索引和当前索引的元素
- 通过
form.setFieldsValue更新表单数据,触发列表重新渲染
- 获取当前
- 按钮禁用处理:给Up按钮添加
disabled={index === 0},Down按钮添加disabled={index === fields.length - 1},防止无效点击。
内容的提问来源于stack exchange,提问作者Ranjan Kumar
相关产品推荐
相关产品推荐

