使用纯HTML5 API实现React拖放

时间:2018-01-27 17:14:16

标签: html5 reactjs drag-and-drop

我试图在不使用任何外部库的情况下实现拖放的简单网格,但是我遇到了一些问题。

首先,我似乎无法使用占位符;我不想将项目彼此交换,我想在用户将执行放置的项目之间创建某种空白空间。 其次,我只能将项目放在列表的末尾。

Here is the fiddle

似乎React + Plain HTML5 DnD API没有很多教程,但在this tutorial之后我尝试通过在类之前声明它然后在onDragOver和OnDragEnd中使用它来设置占位符方法,但它一直在叫我一些关于

的东西
Failed to execute 'insertBefore' on 'Node': parameter 1 is not of type 'Node

此外,在教程中,他将onDragOver设置为ul元素,并且不处理任何onDrop事件,而我在项目上使用onDragOver并在网格容器上调用onDrop。我是这样做的还是我的grid_container只使用onDragOver而不是onDrop?

1 个答案:

答案 0 :(得分:1)

我认为以下代码

onDrop = (e) => {
    e.currentTarget.appendChild(this.state.dragged);
  }

应该是

onDragEnd = (e) => {
    e.currentTarget.appendChild(this.state.dragged);
  }

注意函数名称。因为这是组件中调用的内容

onDragEnd={this.onDragEnd}

但这只是问题的一部分。

您的onDragEnd函数正在尝试将拖动的对象追加到自身。伊克。这不起作用,你不应该试图修改dom。让反应修改它。

所以...... react正在显示this.state.items中的数组。在onDragEndonDragOver中,您需要更新this.state.items以反映订单,然后让反应重新呈现并显示新订单。

使其正常工作的方法

我在github页面上有一个工作示例,显示HTML dnd和react-dnd工作。

github repo

A working example

具有所有善良的component。请参阅src文件夹中的htmlList.js。

当拖动的项目经过另一个项目时,我采用了更新状态数组的方法。所以状态在dragOver中更新。

这是组件的代码。注意,显示的项目数组存储在父组件中,请参阅updateState函数:

此外,还有一个shouldComponentUpdate,因此只有在订单发生变化时才会重新呈现。

注意:我没有使用占位符,因为它不需要。当您将项目拖动到新位置时,整个列表会即时更新。

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import HtmlListItem from './HtmlListItem'
import './List.css'

export default class ListState extends Component {
  static propTypes = {
    data: PropTypes.arrayOf(
      PropTypes.shape({
        id: PropTypes.number.isRequired,
        name: PropTypes.string.isRequired,
        sortIndex: PropTypes.number.isRequired,
      })
    ).isRequired,

    handleDataChanged: PropTypes.func.isRequired,
  }
  static defaultProps = {
    data: {},
  }

  constructor(props) {
    super(props)
    this.state = {
      draggedOverId: undefined,
      beingDragged: undefined,
    }

    this.updateState = this.updateState.bind(this)
    this.dragStart = this.dragStart.bind(this)
    this.dragOver = this.dragOver.bind(this)
    this.dragEnd = this.dragEnd.bind(this)
  }

  shouldComponentUpdate(nextProps, nextState) {
    // only update when the item being hovered changes,
    if (nextState.draggedOverId === this.state.draggedOverId) return false
    return true
  }

  updateState(houses, draggedOverId) {
    this.setState({ draggedOverId: draggedOverId, beingDragged: draggedOverId })

    // update the sortIndex to show the new order
    houses.forEach((house, i) => {
      house.sortIndex = i
    })

    // Tell the parent there is a new order
    this.props.handleDataChanged(houses)
  }

  dragStart(e) {
    // Update our state with the item that is being dragged
    this.setState({ beingDragged: Number(e.target.dataset.id) })
    e.dataTransfer.effectAllowed = 'move'
  }

  dragOver(e) {
    e.preventDefault()

    // ignore when dragging over the list container
    if (e.target.className === 'list') return

    let from = this.state.beingDragged
    let to = Number(e.target.dataset.id)
    this.setState({ draggedOverId: to })

    // reorder the array with the current hover position
    let items = this.props.data
    items.splice(to, 0, items.splice(from, 1)[0])

    this.updateState(items, to)
  }

  dragEnd() {
    // Update state to force removal of the class used for highlighting
    this.updateState(this.props.data, undefined)
  }

  render() {
    const { data } = this.props
    const { draggedOverId } = this.state

    const HtmllistItems = data.map((house, i) => {
      // highlight the new position
      let dragClass = i === draggedOverId ? 'listitem-over' : ''

      return (
        <HtmlListItem
          key={house.id}
          dataId={i}
          className={dragClass}
          text={house.name}
          dragStart={this.dragStart}
          dragEnd={this.dragEnd}
        />
      )
    })

    return (
      <ul className="list" onDragOver={this.dragOver}>
        {HtmllistItems}
      </ul>
    )
  }
}