You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Antd表格列拖拽引发useEffect多次渲染问题求助

React Antd表格列拖拽异常问题解决

问题根源分析

  1. 事件重复绑定导致触发次数翻倍:当cols作为useEffect依赖项时,每次列排序更新cols都会触发useEffect,但每次执行都给所有th标签重复添加拖拽事件监听器,未清理旧监听器,导致同一事件被多次绑定,触发次数指数级增长。
  2. 闭包捕获旧值导致列重置:移除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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 07:24:20