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

如何在React中使用react-dragula触发drop事件并更新数据?

嘿,我刚好做过类似的Trello克隆项目,用react-dragula的时候确实踩过这个坑——直接在构造函数里绑事件肯定不行,因为那时候DOM还没渲染呢!我来给你一步步讲清楚怎么处理:

核心思路:在组件生命周期的合适时机初始化dragula并绑定事件

不管你用类组件还是函数组件,都要等DOM渲染完成后再去操作容器元素,这样才能正确获取到dragula需要的容器,进而绑定drop事件。

1. 先确认依赖安装正确

确保你已经安装了react-dragula和它的核心依赖dragula:

npm install react-dragula dragula
# 或者用yarn
yarn add react-dragula dragula

2. 类组件实现方案

在类组件里,我们用componentDidMount(组件挂载完成,DOM已渲染)初始化dragula实例,用componentWillUnmount清理实例防止内存泄漏:

import React from 'react';
import dragula from 'react-dragula';

class Board extends React.Component {
  constructor(props) {
    super(props);
    this.drake = null; // 保存dragula实例,方便后续销毁
  }

  componentDidMount() {
    // 获取所有Phase容器(给每个Phase容器加统一类名`phase-container`)
    const containers = document.querySelectorAll('.phase-container');
    
    // 初始化dragula实例
    this.drake = dragula(containers);

    // 绑定drop事件,这就是你要找的触发点!
    this.drake.on('drop', (el, target, source) => {
      // el: 被拖动的卡片DOM元素
      // target: 卡片最终落入的目标Phase容器
      // source: 卡片原来所在的Phase容器

      // 从DOM属性里取我们需要的卡片ID和新状态(提前给元素加data属性)
      const cardId = el.dataset.cardId;
      const newStatus = target.dataset.status;

      // 调用你的API更新数据库里的status
      this.updateCardStatus(cardId, newStatus);
    });
  }

  componentWillUnmount() {
    // 组件卸载时销毁dragula实例,避免内存泄漏
    if (this.drake) {
      this.drake.destroy();
    }
  }

  // 封装更新卡片状态的API请求
  updateCardStatus = (cardId, newStatus) => {
    fetch(`/api/cards/${cardId}`, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ status: newStatus }),
    })
    .then(res => res.json())
    .then(data => {
      console.log('卡片状态更新成功:', data);
      // 这里可以更新组件state,让界面同步变化
      // 比如this.setState(prev => ({ cards: ... }))
    })
    .catch(err => console.error('更新失败:', err));
  }

  render() {
    return (
      <div className="board">
        {/* 每个Phase容器要加类名和data-status属性,方便获取状态 */}
        <div className="phase-container" data-status="todo">
          {this.props.cards.filter(c => c.status === 'todo').map(card => (
            <div 
              key={card.id} 
              className="app-item" 
              data-card-id={card.id} // 给卡片加data属性存ID
            >
              {card.title}
            </div>
          ))}
        </div>

        <div className="phase-container" data-status="in-progress">
          {this.props.cards.filter(c => c.status === 'in-progress').map(card => (
            <div key={card.id} className="app-item" data-card-id={card.id}>
              {card.title}
            </div>
          ))}
        </div>

        <div className="phase-container" data-status="done">
          {this.props.cards.filter(c => c.status === 'done').map(card => (
            <div key={card.id} className="app-item" data-card-id={card.id}>
              {card.title}
            </div>
          ))}
        </div>
      </div>
    );
  }
}

export default Board;

3. 函数组件(Hooks)实现方案

如果用函数组件,就用useEffect替代类组件的生命周期,用useRef保存dragula实例:

import React, { useEffect, useRef } from 'react';
import dragula from 'react-dragula';

const Board = ({ cards }) => {
  const drakeRef = useRef(null); // 用ref保存dragula实例

  useEffect(() => {
    // 初始化dragula
    const containers = document.querySelectorAll('.phase-container');
    drakeRef.current = dragula(containers);

    // 定义drop事件处理函数
    const handleDrop = (el, target) => {
      const cardId = el.dataset.cardId;
      const newStatus = target.dataset.status;
      updateCardStatus(cardId, newStatus);
    };

    // 绑定事件
    drakeRef.current.on('drop', handleDrop);

    // 清理函数:组件卸载时销毁实例、移除事件监听
    return () => {
      if (drakeRef.current) {
        drakeRef.current.off('drop', handleDrop);
        drakeRef.current.destroy();
      }
    };
  }, []); // 空依赖数组,只在组件挂载时执行一次

  // 同样封装API请求
  const updateCardStatus = (cardId, newStatus) => {
    fetch(`/api/cards/${cardId}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ status: newStatus }),
    })
    .then(res => res.json())
    .then(data => console.log('更新成功:', data))
    .catch(err => console.error('更新失败:', err));
  };

  // 渲染部分和类组件基本一致
  return (
    <div className="board">
      <div className="phase-container" data-status="todo">
        {cards.filter(c => c.status === 'todo').map(card => (
          <div key={card.id} className="app-item" data-card-id={card.id}>
            {card.title}
          </div>
        ))}
      </div>
      <div className="phase-container" data-status="in-progress">
        {cards.filter(c => c.status === 'in-progress').map(card => (
          <div key={card.id} className="app-item" data-card-id={card.id}>
            {card.title}
          </div>
        ))}
      </div>
      <div className="phase-container" data-status="done">
        {cards.filter(c => c.status === 'done').map(card => (
          <div key={card.id} className="app-item" data-card-id={card.id}>
            {card.title}
          </div>
        ))}
      </div>
    </div>
  );
};

export default Board;

几个关键注意点

  • 不要在构造函数/组件初始化阶段操作DOM:这时候组件还没挂载,容器元素还不存在,肯定获取不到。
  • 一定要清理dragula实例:不管类组件还是函数组件,卸载时都要销毁实例,否则会有内存泄漏问题。
  • 用data-*属性传递数据:这是在DOM事件里获取React组件数据最简单的方式,不需要额外的ref或者状态关联。
  • 如果Phase是动态生成的:比如从state里循环出来的,那要把生成Phase的依赖变量加到useEffect的依赖数组里(类组件就用componentDidUpdate),确保容器更新后重新初始化dragula。

内容的提问来源于stack exchange,提问作者ayeteo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 12:31:32