React中使用dragula拖拽后状态没更新怎么办?

程序员晨曦 阅读 7

我在用dragula实现卡片拖拽功能,但拖拽完成后状态数组没有同步更新。虽然能看到DOM变化,但console.log显示state还是原来的顺序。

尝试过在dragula选项里设置removeOnSpill: true,也手动调用了setState,但都没用。代码大概是这样:


import dragula from 'dragula';

const CardList = ({ cards, setCards }) => {
  useEffect(() => {
    const drake = dragula([document.getElementById('card-container')]);
    drake.on('drop', (el, target, src, sibling) => {
      const newCards = Array.from(target.children).map(card => card.dataset.id);
      setCards(newCards); // 这里没触发更新
    });
    return () => drake.destroy();
  }, []);

  return (
    <div id="card-container">
      {cards.map(id => (
        <div key={id} data-id={id}>{id}</div>
      ))}
    </div>
  );
};

是不是dragula的事件监听和React状态更新机制有冲突?或者需要在某个生命周期里处理?

我来解答 赞 3 收藏
二维码
手机扫码查看
2 条解答
窅恒 ☘︎
这个问题确实是React和dragula的事件机制有点冲突,主要原因是dragula操作的是DOM,而React的状态驱动是单向数据流。一般这样处理:

把状态更新逻辑从dragula的drop事件里抽出来,放到React的生命周期里去管理。具体来说,可以监听dragula的drop事件来获取新的顺序,但不要直接在事件回调里调用setCards,而是通过一个中间变量来暂存新顺序。

修改后的代码大概长这样:


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

const CardList = ({ cards, setCards }) => {
const [newOrder, setNewOrder] = useState([]); // 中间状态
const containerRef = useRef(null);

useEffect(() => {
const container = containerRef.current;
const drake = dragula([container]);

drake.on('drop', () => {
const newCards = Array.from(container.children).map(card => card.dataset.id);
setNewOrder(newCards); // 先更新中间状态
});

return () => drake.destroy();
}, []);

useEffect(() => {
if (newOrder.length) {
setCards(newOrder); // React状态更新放在这里
}
}, [newOrder]);

return (
<div id="card-container" ref={containerRef}>
{cards.map(id => (
<div key={id} data-id={id}>{id}</div>
))}
</div>
);
};


这里的关键点是用了useRef来获取容器元素,避免直接用document.getElementById,更符合React的写法。然后通过两个useState来分离dragula的DOM操作和React的状态更新。

说白了就是让React按自己的节奏来更新状态,别让dragula直接插手。我之前也踩过类似的坑,后来发现这种解耦的方式最稳妥。记得在组件销毁时清理drake实例,不然容易内存泄漏。
点赞
2026-02-18 18:07
❤艳平
❤艳平 Lv1
这个问题主要是因为dragula操作的是DOM,而React的状态更新是基于虚拟DOM的,两者确实会有点冲突。一般这样处理:你需要在dragula触发drop事件后,手动计算新的状态,并且确保setState拿到的是一个全新的数组。

把你的useEffect部分改成这样试试:


useEffect(() => {
const container = document.getElementById('card-container');
const drake = dragula([container]);

const updateOrder = () => {
const newCards = Array.from(container.children).map(card => card.dataset.id);
setCards(prev => {
// 这里做个简单的优化,如果顺序没变就不更新
if (JSON.stringify(prev) === JSON.stringify(newCards)) return prev;
return [...newCards];
});
};

drake.on('drop', updateOrder);

return () => {
drake.off('drop', updateOrder); // 记得清理监听
drake.destroy();
};
}, [setCards]);


这里做了几个改动:首先用prev来对比之前的state,避免不必要的渲染;其次确保返回的是一个新数组,因为React需要引用变化才能触发更新。

另外吐槽一句,dragula虽然好用,但和React这种声明式框架确实有点八字不合。如果你后续要维护更复杂的拖拽场景,可以考虑react-dnd或者react-beautiful-dnd这些专门为React设计的库,能少掉不少头发。

记得给每个卡片加上唯一的data-id属性,这个方案依赖这个属性来重建顺序。如果还是有问题,可能是cards本身不是稳定的id集合,那就需要看看父组件传值有没有问题了。
点赞 5
2026-02-18 00:05