React Antd表格列拖拽引发useEffect多次渲染问题求助
React Antd表格列拖拽异常问题解决
问题根源分析
- 事件重复绑定导致触发次数翻倍:当
cols作为useEffect依赖项时,每次列排序更新cols都会触发useEffect,但每次执行都给所有th标签重复添加拖拽事件监听器,未清理旧监听器,导致同一事件被多次绑定,触发次数指数级增长。 - 闭包捕获旧值导致列重置:移除
cols依赖项后,handleDragStart和handleOnDrop函数会捕获组件初始渲染时的cols值,后续拖拽时始终使用初始的columns数组,导致排序后状态被重置。
解决方案
1. 清理重复的事件监听器
在useEffect的清理函数中移除之前绑定的拖拽事件,避免重复绑定。
2. 使用Ref保存最新的列状态
通过useRef保存cols的最新值,解决闭包捕获旧值的问题;或在更新状态时使用函数式更新,直接获取最新状态值。
3. 优化事件绑定逻辑
仅在表格初始化时绑定一次事件,避免不必要的重复绑定。
修改后的完整代码
import { useState, useEffect, useRef } from 'react'; import { Table } from 'antd'; // 假设columns是预先定义的静态列配置 const columns = [/* 你的列配置 */]; const DraggableTable = () => { const [cols, setCols] = useState(columns); const tableRef = useRef(null); const colsRef = useRef(cols); // 保存最新的cols值 // 同步cols到ref,确保始终拿到最新列状态 useEffect(() => { colsRef.current = cols; }, [cols]); useEffect(() => { if (!tableRef.current) return; const elems = tableRef.current.ownerDocument.querySelectorAll('th'); // 定义事件处理函数(统一管理方便后续移除) const handleDragStart = (e) => { const { innerText } = e.target; const idx = colsRef.current.findIndex((x) => x.title === innerText); e.dataTransfer.setData('colIdx', parseInt(idx)); }; const handleDragOver = (e) => { e.preventDefault(); }; const handleOnDrop = (e) => { const { innerText } = e.target; const droppedColIdx = colsRef.current.findIndex((x) => x.title === innerText); const draggedColIdx = parseInt(e.dataTransfer.getData('colIdx')); // 函数式更新获取最新状态,避免闭包旧值问题 setCols(prevCols => { const tempCols = [...prevCols]; tempCols.splice(droppedColIdx, 0, tempCols.splice(draggedColIdx, 1)[0]); return tempCols; }); }; // 绑定事件到每个th elems.forEach(elem => { elem.setAttribute('draggable', true); elem.addEventListener('dragstart', handleDragStart); elem.addEventListener('dragover', handleDragOver); elem.addEventListener('drop', handleOnDrop); }); // 清理函数:移除所有绑定的事件监听器 return () => { elems.forEach(elem => { elem.removeEventListener('dragstart', handleDragStart); elem.removeEventListener('dragover', handleDragOver); elem.removeEventListener('drop', handleOnDrop); }); }; }, []); // 仅组件挂载时执行一次事件绑定 return <Table ref={tableRef} columns={cols} dataSource={/* 你的数据源 */} />; }; export default DraggableTable;
关键修改点说明
colsRef同步最新列状态:确保拖拽处理函数能获取到实时的列配置,彻底解决闭包捕获旧值的问题。useEffect清理函数:在组件卸载时移除所有事件监听器,防止重复绑定导致的事件触发次数翻倍问题。- 函数式更新状态:
setCols时使用函数式更新,直接获取最新的prevCols,无需依赖闭包中的cols变量。 - 简化依赖项:仅在组件初始化时绑定一次事件,列排序后
th元素会随cols重新渲染,但事件逻辑无需重复绑定。
内容的提问来源于stack exchange,提问作者dor.co
相关产品推荐
相关产品推荐

