react-beautiful-dnd:拖拽结束后状态变更引发异常行为
解决react-beautiful-dnd表格拖拽排序后的重渲染异常问题
从你提供的动图和代码来看,拖拽结束后的异常行为主要由依赖数组索引的回调、状态更新不符合不可变性、不必要的重渲染这几个问题导致,下面是具体的修复方案:
核心问题分析
- 回调函数依赖数组索引:
deleteArticle、setShowPopUp和updateFieldChanged都用数组索引定位元素,拖拽后数组顺序改变,旧索引会指向错误的元素。 - 状态更新未遵循不可变性:如果
onDragEnd中直接修改原数组,React无法正确识别状态变化,引发重渲染异常。 - 不必要的重渲染:
updateFieldChanged(index)每次渲染都会生成新函数,触发组件无意义重渲染,干扰拖拽后的状态同步。
具体修复步骤
1. 替换索引依赖为唯一ID
所有操作元素的回调都改用元素的_id(唯一标识)来定位,避免拖拽后索引失效:
- 修改
setShowPopUp和deleteArticle的调用:// 原代码:onClick={() => setShowPopUp(index)} onClick={() => setShowPopUp(element._id)} // 原代码:onClick={() => deleteArticle(index)} onClick={() => deleteArticle(element._id)} - 父组件中对应函数也要改为根据ID处理:
// deleteArticle示例 const deleteArticle = (id) => { setArticles(prev => prev.filter(item => item._id !== id)); }; // setShowPopUp示例 const setShowPopUp = (id) => { const targetItem = Articles.find(item => item._id === id); // 处理弹窗逻辑 };
2. 优化updateFieldChanged的回调方式
避免每次渲染生成新函数,改用函数柯里化+唯一ID的方式,同时保证状态更新的不可变性:
- 父组件中重构
updateFieldChanged:import { useCallback } from 'react'; const updateFieldChanged = useCallback((id) => (e) => { const { name, value } = e.target; setArticles(prev => prev.map(item => item._id === id ? {...item, [name]: value} : item )); }, []); - 表格组件中调用时传递元素ID:
// 原代码:onChange={updateFieldChanged(index)} onChange={updateFieldChanged(element._id)}
3. 确保onDragEnd的状态更新符合不可变性
拖拽排序时必须创建新数组,不能直接修改原数组:
import { useCallback } from 'react'; const onDragEnd = useCallback((result) => { if (!result.destination) return; // 复制原数组,避免直接修改 const items = Array.from(Articles); const [reorderedItem] = items.splice(result.source.index, 1); items.splice(result.destination.index, 0, reorderedItem); setArticles(items); }, [Articles]);
4. 优化组件重渲染性能
用React.memo包裹表格组件,配合父组件中useCallback稳定回调函数,减少不必要的重渲染:
// 表格组件末尾 export default React.memo(EditCategoryTable);
修改后的完整组件代码
import React from "react"; import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; const EditCategoryTable = ({ onDragEnd, Articles, updateFieldChanged, setShowPopUp, deleteArticle, }) => { return ( <table className="articles_table"> <thead> <tr> <th style={{ width: "5%" }}></th> <th style={{ width: "55%" }}>Article</th> <th style={{ width: "20%" }}>Price</th> <th style={{ width: "10%" }}></th> <th style={{ width: "10%" }}></th> </tr> </thead> <DragDropContext onDragEnd={onDragEnd}> <Droppable droppableId="droppable-1"> {(provided, _) => ( <tbody ref={provided.innerRef} {...provided.droppableProps}> {Articles.map((element, index) => ( <Draggable key={element._id} draggableId={`draggable-${element._id}`} index={index} > {(provided, snapshot) => ( <tr ref={provided.innerRef} {...provided.draggableProps} style={{ ...provided.draggableProps.style, boxShadow: snapshot.isDragging ? "0 0 .4rem #666" : "none", }} > <td className="Movebuttons"> <i className="fa-solid fa-bars" {...provided.dragHandleProps} ></i> </td> <td> <input className="articles_table_input" type="text" id="name" name="name" value={element.name || ""} onChange={updateFieldChanged(element._id)} /> </td> <td> <input className="noscroll articles_table_input" type="number" id="price" name="price" value={element.price || ""} onChange={updateFieldChanged(element._id)} /> </td> <td> <i className="fa-regular fa-pen-to-square fa-lg" style={{ color: "#018a19" }} onClick={() => setShowPopUp(element._id)} ></i> </td> <td> <i className="fa-solid fa-trash fa-lg" onClick={() => deleteArticle(element._id)} style={{ color: "#454545" }} ></i> </td> </tr> )} </Draggable> ))} {provided.placeholder} </tbody> )} </Droppable> </DragDropContext> </table> ); }; export default React.memo(EditCategoryTable);
内容的提问来源于stack exchange,提问作者Zidane Hammouda
相关产品推荐
相关产品推荐

