react-beautiful-dnd嵌套拖拽时setState前props被异常修改问题
嵌套拖拽跨分组状态同步异常根因
- 核心问题是嵌套状态的浅拷贝未切断引用关联,违反React不可变数据原则
之前做的props拷贝仅处理了最外层对象,内层Rank数组、Group对象在多个Overall Group间存在内存引用共享。处理Rank拖拽时使用splice等方法原地修改数组,直接改动了共享内存中的数据,不需要等setState调用,所有引用该内存地址的props/state都会立刻同步变更,这就是观察到「setState调用前props已经被修改」的直接原因。 - 复用的
onDragEnd方法未做更新范围隔离
处理内层Rank拖拽时,逻辑没有限定仅修改当前激活的Overall Group下的对应数据,错误操作了跨分组共享的源数据,导致Rank排序改动同步到所有分组。 - 之前尝试的两个排查方案未命中根因:组件key仅解决React组件复用问题,外层浅拷贝无法解决嵌套层级的引用共享问题,自然无法修复异常。
修复方案
- 严格遵循不可变数据更新规则,所有状态修改操作禁止直接触碰原state/props对象,处理嵌套更新时逐层做拷贝:
处理Rank排序时,依次拷贝overallGroups数组、当前激活的目标Overall Group对象、Rank所属的Group对象、Rank数组,在全新拷贝的对象上完成排序操作,从根本上避免修改共享引用。参考实现:const onDragEnd = (result) => { const { source, destination, type } = result; if (!destination) return; // 区分拖拽层级 if (type === 'GROUP') { // 外层Group拖拽逻辑:仅更新当前激活分组的groups排序,全程不修改原对象 const newGroups = Array.from(activeOverallGroup.groups); const [removed] = newGroups.splice(source.index, 1); newGroups.splice(destination.index, 0, removed); const newOverallGroups = overallGroups.map(g => { return g.id === activeGroupId ? {...g, groups: newGroups} : g; }) setOverallGroups(newOverallGroups); return; } if (type === 'RANK') { // 内层Rank拖拽逻辑:逐层拷贝,仅修改当前激活分组下对应Group的ranks const newOverallGroups = overallGroups.map(overallGroup => { // 非当前激活的分组直接返回原对象,不做任何修改 if (overallGroup.id !== activeOverallGroupId) return overallGroup; return { ...overallGroup, groups: overallGroup.groups.map(group => { // 非拖拽所属的Group直接返回原对象 if (group.id !== source.droppableId) return group; // 拷贝Rank数组后再做排序操作 const newRanks = Array.from(group.ranks); const [removed] = newRanks.splice(source.index, 1); newRanks.splice(destination.index, 0, removed); return {...group, ranks: newRanks}; }) } }) setOverallGroups(newOverallGroups); } } - 从初始化源头切断引用共享:初始化每个Overall Group的groups、ranks数据时,不要复用同一个初始数组/对象变量,对初始嵌套数据做深拷贝后再赋值给对应分组,避免初始状态就存在引用共享。
- 给不同层级的Droppable设置独立的
type属性(外层Group类型设为GROUP,内层Rank类型设为RANK),配合onDragEnd中的类型判断,确保不同层级的拖拽逻辑互不干扰。
内容的提问来源于stack exchange,提问作者Frank
相关产品推荐
相关产品推荐

