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

React实现拖拽功能遇报错:onDragEnter需为函数而非字符串

React拖拽功能实现问题排查与修复

嘿,作为React新手,你遇到的这个报错其实是很常见的入门坑,咱们一步步来拆解问题、修复代码:

核心错误原因

控制台提示Expected onDragEnter listener to be a function, instead got a value of string type,是因为你在React元素里用了原生HTML的字符串式事件绑定(比如onDragEnter="drag_enter(event)"),但React的事件系统要求事件处理函数必须是函数引用,不能是字符串。

除此之外,你还混用了原生拖拽API和react-dnd库,代码结构也比较混乱,咱们一步步理顺:

修复步骤与完整代码

1. 清理冗余代码,统一使用react-dnd

你已经引入了react-dnd和HTML5后端,但完全没用到它的核心API(DragSource、DropTarget),反而写了一堆原生拖拽的全局函数,这会导致冲突。咱们基于react-dnd来实现拖拽功能,更符合React的开发模式。

2. 正确绑定React事件

React里的事件绑定要直接传递函数,不能用字符串,也尽量避免直接操作DOM(比如getElementById),改用组件状态或refs来管理。

完整修正代码

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { DragSource, DropTarget } from 'react-dnd';
import { DragDropContext } from 'react-dnd-html5-backend';

// 定义拖拽类型常量
const ItemTypes = {
  COMPONENT: 'component'
};

// 拖拽源配置:定义拖拽行为
const componentSource = {
  beginDrag(props) {
    return {
      name: props.name
    };
  }
};

// 拖拽源收集函数:注入拖拽相关props
function collectDragSource(connect, monitor) {
  return {
    connectDragSource: connect.dragSource(),
    isDragging: monitor.isDragging()
  };
}

// 可拖拽的列表项组件
class DraggableItem extends Component {
  static propTypes = {
    name: PropTypes.string.isRequired,
    connectDragSource: PropTypes.func.isRequired,
    isDragging: PropTypes.bool.isRequired
  };

  render() {
    const { name, connectDragSource, isDragging } = this.props;
    // 用connectDragSource包装可拖拽元素
    return connectDragSource(
      <li 
        className="list-group-item"
        style={{ opacity: isDragging ? 0.5 : 1, cursor: 'move' }}
      >
        <a href="#">{name}</a>
      </li>
    );
  }
}

// 用DragSource包装组件
const DraggableComponent = DragSource(ItemTypes.COMPONENT, componentSource, collectDragSource)(DraggableItem);

// 放置目标配置:定义放置行为
const dropZoneTarget = {
  drop(props, monitor) {
    const item = monitor.getItem();
    // 触发父组件的回调,更新放置的组件列表
    props.onDrop(item.name);
  }
};

// 放置目标收集函数:注入放置相关props
function collectDropTarget(connect, monitor) {
  return {
    connectDropTarget: connect.dropTarget(),
    isOver: monitor.isOver()
  };
}

// 放置区域组件
class DropZone extends Component {
  static propTypes = {
    connectDropTarget: PropTypes.func.isRequired,
    isOver: PropTypes.bool.isRequired,
    onDrop: PropTypes.func.isRequired
  };

  render() {
    const { connectDropTarget, isOver } = this.props;
    // 用connectDropTarget包装放置区域
    return connectDropTarget(
      <div
        id="drop_zone"
        className="drop-zone"
        style={{ 
          padding: '20px', 
          border: isOver ? '2px dashed green' : '2px dashed gray',
          minHeight: '100px'
        }}
      >
        {isOver ? '松开放置组件' : '拖拽组件到这里'}
      </div>
    );
  }
}

// 用DropTarget包装组件
const DropZoneComponent = DropTarget(ItemTypes.COMPONENT, dropZoneTarget, collectDropTarget)(DropZone);

// 主表单组件
class BasicForm extends Component {
  constructor(props) {
    super(props);
    this.state = {
      status: 'App status...',
      droppedComponents: []
    };
    this.handleDrop = this.handleDrop.bind(this);
  }

  // 处理放置事件,更新状态
  handleDrop(componentName) {
    this.setState(prevState => ({
      status: `已放置 ${componentName} 到区域`,
      droppedComponents: [...prevState.droppedComponents, componentName]
    }));
  }

  render() {
    const componentList = [
      'File Reader', 'File Move', 'Data Base', 'SAP R3', 
      'FTP', 'SOAP', 'Rest', 'Merge', 'Filter', 'Transform'
    ];

    return (
      <div style={{ fontFamily: 'sans-serif', textAlign: 'center' }}>
        <form onSubmit={(e) => e.preventDefault()}> {/* 阻止默认提交行为 */}
          <div className="container">
            <div className="row">
              <div className="col-sm-4">
                <ul className="list-group">
                  {/* 渲染可拖拽组件 */}
                  {componentList.map(name => (
                    <DraggableComponent key={name} name={name} />
                  ))}
                </ul>
              </div>
              <div className="col-sm-8">
                <h2 id="app_status">{this.state.status}</h2>
                <h1>Drop Zone</h1>
                {/* 渲染放置区域 */}
                <DropZoneComponent onDrop={this.handleDrop} />
                {/* 显示已放置的组件 */}
                {this.state.droppedComponents.length > 0 && (
                  <div className="mt-3">
                    <h3>已放置的组件:</h3>
                    <ul className="list-group">
                      {this.state.droppedComponents.map((name, idx) => (
                        <li key={idx} className="list-group-item">{name}</li>
                      ))}
                    </ul>
                  </div>
                )}
                <input type="submit" value="Submit" className="mt-3" />
              </div>
            </div>
          </div>
        </form>
      </div>
    );
  }
}

// 用DragDropContext包装主组件,启用拖拽功能
export default DragDropContext(BasicForm);

关键改动说明

  • 事件绑定修正:完全移除了原生的字符串事件绑定,改用react-dnd的connectDragSource和connectDropTarget来处理拖拽逻辑,避免了React事件系统的冲突。
  • 组件拆分:把可拖拽项和放置区域拆成独立组件,更符合React的组件化思想,也便于维护。
  • 状态管理:用组件状态state来管理拖拽状态和已放置的组件,不再直接操作DOM,符合React的数据流模式。
  • react-dnd正确使用:给拖拽源和放置目标分别配置了DragSource和DropTarget,并通过collect函数注入必要的props,实现了完整的拖拽功能。

额外提示

  • 记得确保你的项目已经正确安装了react-dnd和react-dnd-html5-backend(npm install react-dnd react-dnd-html5-backend)。
  • 不要混用原生拖拽API和react-dnd,两者的事件系统会冲突,选一种方式实现即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:15:33