React中使用dragula拖拽到新容器后数据不同步怎么办?

令狐卫利 阅读 28

我在用dragula实现两个列表之间的拖拽功能,但把元素拖到另一个容器后,状态里的数据没跟着更新,卡了好久

代码是这样的:


import { dragula } from 'dragula';

function ListContainer() {
  const [items, setItems] = useState([]);
  const [targetItems, setTargetItems] = useState([]);

  useEffect(() => {
    const bag = dragula([document.getElementById('list1'), document.getElementById('list2')]);
    return () => bag.destroy();
  }, []);

  return (
    <div>
      <div id="list1">{items.map(item => <div key={item.id}>{item.name}</div>)}</div>
      <div id="list2">{targetItems.map(item => <div key={item.id}>{item.name}</div>)}</div>
    </div>
  );
}

我已经尝试在dragula实例里加drop事件监听,但修改状态时发现新旧元素数据拿不到,控制台也没报错,求大神指点

我来解答 赞 12 收藏
二维码
手机扫码查看
1 条解答
Des.心霞
这个问题的根本原因是dragula库本身是一个纯粹的DOM操作库,它只负责处理DOM元素的拖拽,而不会自动帮你同步React的状态。React的状态和DOM是两个独立的东西,dragula修改了DOM,但React的状态并没有感知到这些变化,所以你的状态没有更新。

要解决这个问题,我们需要手动监听dragula的事件,并在事件回调中同步React的状态。下面是具体的解决方案:

首先,在初始化dragula的时候,添加对drop事件的监听。这个事件会在元素被拖拽到新容器并放置后触发。通过这个事件,我们可以拿到拖拽的元素、源容器和目标容器的信息。

然后,根据这些信息手动更新React的状态。我们需要把源容器和目标容器的数据重新计算一遍,并用setItemssetTargetItems来更新状态。

来看代码实现:

import { dragula } from 'dragula';
import { useState, useEffect } from 'react';

function ListContainer() {
const [items, setItems] = useState([{ id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }]);
const [targetItems, setTargetItems] = useState([]);

useEffect(() => {
// 初始化dragula
const bag = dragula([document.getElementById('list1'), document.getElementById('list2')]);

// 监听drop事件
bag.on('drop', (el, target, source) => {
// el是被拖拽的元素,target是目标容器,source是源容器
const itemId = parseInt(el.dataset.id, 10); // 假设每个元素有一个data-id属性标识唯一id

if (source.id === 'list1' && target.id === 'list2') {
// 从list1拖到list2
const newItem = items.find(item => item.id === itemId);
setItems(items.filter(item => item.id !== itemId)); // 从原列表移除
setTargetItems([...targetItems, newItem]); // 添加到目标列表
} else if (source.id === 'list2' && target.id === 'list1') {
// 从list2拖回list1
const newItem = targetItems.find(item => item.id === itemId);
setTargetItems(targetItems.filter(item => item.id !== itemId)); // 从原列表移除
setItems([...items, newItem]); // 添加到目标列表
}
});

return () => bag.destroy(); // 组件卸载时销毁dragula实例
}, [items, targetItems]); // 注意依赖项,确保状态是最新的

return (
<div>
<div id="list1">
{items.map(item => (
<div key={item.id} data-id={item.id}>{item.name}</div>
))}
</div>
<div id="list2">
{targetItems.map(item => (
<div key={item.id} data-id={item.id}>{item.name}</div>
))}
</div>
</div>
);
}


详细说明
1. 数据标识:为了让React能够正确识别每个元素,我们在渲染的时候给每个元素加了一个data-id属性,值是元素的唯一id。这样在drop事件里可以通过el.dataset.id拿到对应的id。

2. 状态更新逻辑:根据源容器和目标容器的id判断拖拽的方向,然后分别处理两种情况:
- 从list1拖到list2:从items中移除对应元素,并将其添加到targetItems
- 从list2拖回list1:从targetItems中移除对应元素,并将其添加回items

3. 依赖项问题:useEffect的依赖数组里需要包含itemstargetItems,否则在状态更新后,事件回调里的数据会是旧的,导致状态不同步。

4. 性能优化:虽然这里直接用了findfilter来操作数组,但如果数据量特别大,可以考虑用Map或其他数据结构优化查找和更新的性能。

这样做之后,React的状态就会和DOM保持同步了。不过说实话,dragula这种库其实更适合纯DOM操作的场景,如果你完全用React生态的话,建议可以看看像react-dnd这样的库,它们天生就和React的状态管理更契合。
点赞 1
2026-02-14 12:24