React中map渲染表单输入框 修改单个时所有字段联动如何解决
问题现象
在React项目中使用Ant Design Form组件开发时,通过Array.map方法渲染3组范围输入项,每组包含绑定name为startRange、endRange的两个InputNumber输入框。实际操作时修改任意一个输入框的值,所有同name属性的输入框都会同步变化,给map渲染的外层div元素添加key属性后问题仍然存在。
问题代码
const App = () => { const List = [ ["1", "2"], ["3", "4"], ["10 ", "20"] ]; return ( <> <Form layout="vertical" requiredMark={false}> {List.map((l, index) => ( <div style={{ display: "flex", alignItems: "center" }} key={index}> <Form.Item name="startRange"> <InputNumber defaultValue={l[0]} style={{ width: "70px", height: "43px", borderRadius: "4.5px", border: "1px solid #CBCBCB" }} min={1} max={100} /> </Form.Item> <p style={{ marginLeft: "15px", marginRight: "12px" }}>To</p> <Form.Item name="endRange"> <InputNumber defaultValue={l[1]} style={{ width: "70px", height: "43px", borderRadius: "4.5px", border: "1px solid #CBCBCB" }} min={1} max={100} /> </Form.Item> </div> ))} </Form> </> ); };
问题根因
Ant Design Form内部以Form.Item的name属性作为唯一键存储表单字段状态,同一个Form实例下,相同name的Form.Item会被识别为同一个字段,共享同一份状态值,因此修改其中一个会触发所有同名字段同步更新。
给外层div加key仅用于React做DOM节点的diff对比,不会影响Form内部的字段状态映射逻辑,因此无法解决该问题。
解决方案
核心原则:保证同一个Form下每个Form.Item的name属性全局唯一,有两种常用实现方式:
方案1:手动给name追加索引标识(改动最小)
在map循环时,给每组的两个字段name拼接当前循环索引,确保每个字段名不重复即可。如果需要后续统一取值,推荐使用数组格式的name路径,把多组范围值存在同一个字段下:
const App = () => { const List = [ ["1", "2"], ["3", "4"], ["10 ", "20"] ]; const [form] = Form.useForm(); return ( <> <Form form={form} layout="vertical" requiredMark={false}> {List.map((l, index) => ( <div style={{ display: "flex", alignItems: "center" }} key={index}> {/* 用数组路径做name,最终值会存在rangeList字段下,对应索引项的start属性 */} <Form.Item name={['rangeList', index, 'startRange']} initialValue={l[0]}> <InputNumber style={{ width: "70px", height: "43px", borderRadius: "4.5px", border: "1px solid #CBCBCB" }} min={1} max={100} /> </Form.Item> <p style={{ marginLeft: "15px", marginRight: "12px" }}>To</p> <Form.Item name={['rangeList', index, 'endRange']} initialValue={l[1]}> <InputNumber style={{ width: "70px", height: "43px", borderRadius: "4.5px", border: "1px solid #CBCBCB" }} min={1} max={100} /> </Form.Item> </div> ))} </Form> </> ); };
注意:原代码中把默认值写在InputNumber的
defaultValue属性上,在Form.Item绑定name后不生效,需要把初始值写在Form.Item的initialValue上,或者统一在Form的initialValues中配置。
方案2:使用官方Form.List组件(推荐动态表单场景使用)
如果是动态增删的表单项列表,直接使用Ant Design内置的Form.List组件,组件会自动管理字段name和key,不需要手动处理索引拼接:
const App = () => { const initialList = [ ["1", "2"], ["3", "4"], ["10 ", "20"] ]; return ( <> <Form layout="vertical" requiredMark={false} initialValues={{ rangeList: initialList.map(item => ({ startRange: item[0], endRange: item[1] })) }} > <Form.List name="rangeList"> {(fields) => ( <> {fields.map(({ key, name, ...restField }) => ( <div style={{ display: "flex", alignItems: "center" }} key={key}> <Form.Item {...restField} name={[name, 'startRange']}> <InputNumber style={{ width: "70px", height: "43px", borderRadius: "4.5px", border: "1px solid #CBCBCB" }} min={1} max={100} /> </Form.Item> <p style={{ marginLeft: "15px", marginRight: "12px" }}>To</p> <Form.Item {...restField} name={[name, 'endRange']}> <InputNumber style={{ width: "70px", height: "43px", borderRadius: "4.5px", border: "1px solid #CBCBCB" }} min={1} max={100} /> </Form.Item> </div> ))} </> )} </Form.List> </Form> </> ); };
两种方案修改后,每个输入框对应独立的字段状态,修改单个输入框不会影响其他输入框的值。
内容的提问来源于stack exchange,提问作者Sai Krishnadas

