react-beautiful-dnd拖拽列表重排时出现闪烁问题如何解决
问题原因
- 直接对Redux返回的
lists原数组执行sort()方法:Array.sort()是原地排序方法,会直接修改原数组的引用,违反Redux状态不可变原则,同时每次组件重渲染都会触发一次排序,导致列表重复渲染闪烁。 - 拖拽结束后遍历所有列表项逐个触发
updateListdispatch:多次批量更新状态会触发组件多次重渲染,同时你在useEffect中监听了currentId,每次更新列表后会重新触发getLists拉取后端数据,相当于拖拽后先修改本地状态、再拉取后端状态两次更新,中间的时间差产生闪烁。 - 未做乐观更新:拖拽逻辑完全依赖后端返回的最新列表数据,拖拽结束后没有先在本地更新视图,等待后端返回的空窗期视图还是旧状态,数据返回后才切换为新状态,产生跳闪。
- 修改列表项时直接操作原引用:浅拷贝
lists得到的newState里的列表项还是原对象的引用,直接修改list.order相当于直接修改Redux store中的原状态,导致状态不可预期。 - 数据类型不匹配:样例数据中
order字段是字符串类型,排序时直接做减法运算虽然会隐式转换,但部分场景下可能出现排序异常导致列表跳动。
解决方案
- 排序前先拷贝原数组,不要直接修改Redux返回的原状态:
// 替换原来的 lists.sort(newArrange); 逻辑 const newArrange = (a,b) => { return Number(a.order) - Number(b.order); } const sortedLists = [...lists].sort(newArrange); // 渲染map时用sortedLists,不要用原lists
- 优化拖拽结束后的更新逻辑,禁止直接修改原对象引用,先做本地乐观更新,再批量同步后端,避免重复拉取列表:
const onDragEnd = (result) => { const { destination, source, type } = result; if(!destination) return; if(type !== 'list') return; // 深拷贝列表项避免修改原引用 const newState = lists.map(item => ({...item})); const [dragList] = newState.splice(source.index, 1); newState.splice(destination.index, 0, dragList); // 更新order字段 const updateBatch = newState.map((list, index) => { list.order = index; // 收集所有更新请求批量发送,不要逐个dispatch return updateList(list._id , { ...list }); }); // 批量执行所有更新请求,全部完成后再拉取一次列表对齐状态 Promise.all(updateBatch.map(dispatch)).then(() => { dispatch(getLists()); }); }
- 调整
useEffect的依赖项,避免不必要的列表拉取:如果currentId和列表数据无关,把它从依赖项中移除,仅在组件初始化时拉取一次列表。 - 给
List组件包裹React.memo,避免父组件重渲染时所有列表项无意义重渲染:
const List = React.memo((props) => { // 原有List逻辑 });
- 检查列表容器的样式,确保
listContainer设置了position: relative,保证react-beautiful-dnd的占位符位置计算正确,不会出现布局跳动。
内容的提问来源于stack exchange,提问作者rona
相关产品推荐
相关产品推荐

