React useEffect依赖data重渲染时Antd表单Input内容清空问题

问题表现
当前UI存在重渲染清空输入框的问题:每次data状态变化触发组件重渲染时,输入框内已经填好的内容就会被清空,相关触发逻辑如下:
useEffect(() => { display(); }, [data]);
data不能从依赖项里移除——新增、删除区间操作后必须触发重渲染,才能展示最新的列表结构。现在的现象是初始加载时输入框能正常显示预设值,只要删除任意一个区间(比如3-4这个区间),所有输入框的内容都会被清空。
问题根因
三个写法问题共同导致了这个异常:
- 列表渲染直接拿数组下标
index当节点key:删除中间的区间项后,后面所有项的下标都会往前移,React做虚拟DOM对比时会认错节点,加上Antd Form是靠Form.Item的name属性匹配字段值,下标变了之后name跟着变,原有值匹配不上自然就显示为空。 - 删除方法直接修改原始state:
deleteNewRange里直接对data.Red.List调用splice,违反React状态不可变的要求,会导致状态更新链路异常,重渲染时拿不到正确的旧值。 - 每次
data变化就调用display()全量重置表单:相当于每次列表变更都把表单值重新覆盖一遍,删除项之后字段名和表单项的对应关系已经错乱,重置时匹配不到值就直接显示空。
修复方法
- 给每个区间项分配一个固定不变的唯一标识,渲染时用这个标识当
key,同时Form.Item的name也绑定这个标识,不要跟着数组下标变化。唯一标识可以用第三方库生成,也可以自己维护一个自增数字实现,只要每个项的id不重复、不随列表位置变动即可。 - 修正删除逻辑,全程不要直接修改原state对象,通过不可变更新的方式生成新的状态对象再调用
setData。 - 不要在
data变化时全量重置表单,初始化表单值的逻辑只在组件首次加载时执行一次即可,新增项时单独给新项设置初始值,不要全量覆盖已有字段的输入内容。
修复后核心代码参考
// 可自行替换为更严谨的唯一id生成逻辑,比如引入uuid库、维护自增数字 const generateId = () => Math.random().toString(36).slice(2); const App = () => { // 初始化时给每个区间项绑定固定id const [data, setData] = useState({ Red: { Name: "R", List: [ { id: generateId(), range: ["4", "6"] }, { id: generateId(), range: ["3", "4"] }, { id: generateId(), range: ["1", "2"] }, { id: generateId(), range: ["7", "10"] } ] } }); const [form] = Form.useForm(); const deleteNewRange = (targetId) => { // 用filter返回新数组,不修改原state const newList = data.Red.List.filter(item => item.id !== targetId); setData({ ...data, Red: { ...data.Red, List: newList } }); }; // 仅首次加载时初始化表单值 useEffect(() => { const initValues = {}; data.Red.List.forEach(item => { initValues[`startRange_${item.id}`] = item.range[0]; initValues[`endRange_${item.id}`] = item.range[1]; }); form.setFieldsValue(initValues); }, []); return ( <> <Form form={form}> {data.Red.List.map((item) => ( // 用固定id作为key,Form.Item的name也绑定id,不会因为删除其他项变化 <div key={item.id}> <Form.Item name={`startRange_${item.id}`}> <InputNumber /> </Form.Item> <p>To</p> <Form.Item name={`endRange_${item.id}`}> <InputNumber/> </Form.Item> <div>Add</div> <div onClick={() => deleteNewRange(item.id)}> Delete </div> </div> ))} </Form> <Button type="primary" htmlType="submit"> Save </Button> </> ); };
内容的提问来源于stack exchange,提问作者Sai Krishnadas
相关产品推荐
相关产品推荐

