You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

react-resizable+react-drag-listview+AntD表格拖拽异常Bug修复咨询

我之前在结合react-resizable和react-drag-listview实现Ant Design表格的列宽调整+拖拽功能时,碰到过完全一样的Bug!这个问题本质上是两个库的事件监听逻辑和DOM元素识别冲突导致的,尤其是快速连续操作时特别容易触发。下面是我亲测有效的几个解决办法:

解决方案1:强制拖拽触发目标为表头单元格

react-drag-listview默认会把点击的最内层DOM元素(比如表头里的文本节点)当成拖拽触发源,这就导致列宽调整后,DOM结构或样式变化时,触发源可能变成了文本而非整个表头单元格。我们可以通过配置拖拽组件的handle选项,强制指定只有表头单元格(<th>元素)才能触发拖拽:

import ReactDragListView from 'react-drag-listview';

// 定义表头组件,给<th>添加统一的类名
const TableHeader = () => (
  <tr>
    {columns.map(col => (
      <th key={col.key} className="table-header-cell">
        <Resizable 
          width={col.width} 
          onResize={(e, { size }) => updateColumnWidth(col.key, size.width)}
        >
          <span>{col.title}</span>
        </Resizable>
      </th>
    ))}
  </tr>
);

// 初始化拖拽表头时,指定handle为表头单元格的类选择器
const DraggableHeader = ReactDragListView.DragColumn(TableHeader, {
  handle: '.table-header-cell', // 只有点击这个类的元素才会触发拖拽
  onDragEnd: (fromIndex, toIndex) => {
    // 处理列拖拽后的位置交换逻辑
    reorderColumns(fromIndex, toIndex);
  }
});

这样配置后,不管点击表头里的文本还是子元素,拖拽都会绑定到外层的<th>单元格上,彻底避免触发源错误的问题。

解决方案2:列宽调整后强制重渲染拖拽组件

react-resizable在调整列宽时,会修改表头单元格的stylewidth属性,可能导致react-drag-listview的事件监听绑定到了旧的DOM状态上。我们可以在列宽调整完成后,强制触发拖拽组件的重渲染,让它重新绑定事件到最新的DOM元素上:

// 函数组件示例,用一个状态变量触发重渲染
const [dragRefreshKey, setDragRefreshKey] = useState(0);

const updateColumnWidth = (key, newWidth) => {
  // 原有更新列宽的逻辑
  setColumns(prev => prev.map(col => col.key === key ? {...col, width: newWidth} : col));
  // 更新key,触发拖拽组件重渲染
  setDragRefreshKey(prev => prev + 1);
};

// 把dragRefreshKey作为key传给拖拽表头组件
<DraggableHeader key={dragRefreshKey} />

通过给拖拽组件添加动态key,每次列宽调整后更新这个key,React会重新创建组件实例,确保react-drag-listview的事件监听绑定在最新的DOM元素上。

解决方案3:阻止resize手柄的事件冒泡

react-resizable的调整手柄在触发mousedown事件时,如果冒泡到外层的拖拽组件,可能会干扰拖拽的事件判断。我们可以自定义resize手柄,阻止它的mousedown事件冒泡:

// 自定义resize手柄组件
const CustomResizeHandle = () => (
  <div 
    className="react-resizable-handle"
    onMouseDown={(e) => {
      // 阻止事件冒泡,避免被拖拽组件捕获
      e.stopPropagation();
    }}
  />
);

// 在表头的Resizable组件中使用自定义手柄
<Resizable 
  width={col.width} 
  onResize={(e, { size }) => updateColumnWidth(col.key, size.width)}
  handle={<CustomResizeHandle />}
>
  <span>{col.title}</span>
</Resizable>

这样调整列宽时,resize的事件不会传递给拖拽组件,从源头减少两个库的事件冲突。

解决方案4:用状态隔离resize和拖拽操作

如果上面的方法都无法彻底解决,可以通过一个全局状态来控制两个操作的触发时机,确保同一时间只有一个操作能生效:

const [isResizing, setIsResizing] = useState(false);

const handleResizeStart = () => {
  // 开始调整列宽时,标记状态
  setIsResizing(true);
};

const handleResizeEnd = (e, { size }) => {
  updateColumnWidth(col.key, size.width);
  // 列宽调整完成后,清除状态
  setIsResizing(false);
};

// 拖拽组件的onDragStart回调中,判断是否正在resize
const handleDragStart = () => {
  if (isResizing) {
    return false; // 阻止拖拽触发
  }
  // 正常拖拽逻辑
};

// 配置拖拽组件
const DraggableHeader = ReactDragListView.DragColumn(TableHeader, {
  onDragStart: handleDragStart,
  onDragEnd: (fromIndex, toIndex) => reorderColumns(fromIndex, toIndex)
});

// Resizable组件绑定resize开始和结束事件
<Resizable 
  width={col.width} 
  onResizeStart={handleResizeStart}
  onResize={handleResizeEnd}
>
  <span>{col.title}</span>
</Resizable>

这个方法通过状态隔离两个操作,避免快速操作时的事件冲突。

优先尝试方案1和方案2,这两个是最直接有效的,大部分情况下能解决问题。如果还是偶发Bug,可以结合方案3和4一起使用。

内容的提问来源于stack exchange,提问作者Mr. Robot

火山引擎 最新活动