使用 React 进行拖放(无需库)修订版

2025-06-07

使用 React 进行拖放(无需库)修订版

简介

在本教程中,我将展示如何使用 React 从头开始​​创建无需第三方库的拖放组件。

我已经发表了一篇关于 React 中的拖放功能的文章,为什么还要再写一篇新的呢?

嗯,我们很多人学习新事物时,总以为自己已经理解了。但随着学习的深入,我们意识到还有很多东西需要学习。所以我用更好的方法制作了一个新的拖放功能。尽情享受吧!

重要信息

  1. 存在e.dataTransfer有助于拖放功能的事件处理程序,但由于我们使用 React,我发现使用状态更简单。

  2. 请务必查看代码沙盒。我可能会添加一些下面未反映的内容,但下面的代码是完整的。

  3. 你可能知道更好的方法!如果你觉得可以改进代码,请评论。

HTML5 中的拖放功能

我们将使用一些新元素,但我们不会使用所有的 HTML5 拖放元素。

  1. draggable使 div可拖动(而不是单击并拖动时突出显示)
  2. onDragStart当你开始拖动时触发一次
  3. onDragEnter当拖动的 div进入另一个 div 时触发一次。
  4. onDragOver拖动到div上持续触发
  5. onDrop鼠标点击被释放时触发

我们将把其中的最后 4 个传递给 JavaScript,以赋予它 DND 逻辑。

入门。

让我们制作一些groups可以拖动的元素和一些item可以拖动的元素。

Dnd.js



import React, { useState } from "react";
import "./Dnd.scss";

export default function Dnd() {

  // my groups to be dragged between

  const groups = ["group1", "group2", "group3", "noDrop"];

  // My items to be dragged around

  const initialItems = [
    { id: 1, group: "group1", value: "drag 1" },
    { id: 2, group: "group1", value: "drag 2" },
    { id: 3, group: "group1", value: "drag 3" }
  ];

  return (
    <>

      // Creating the group divs 

      <div className="groups">
        {groups.map((group) => (
          <div className="group">
            <h1 className="title">{group}</h1>
            <div>

              // Creating our items to drag and drop

              {items
                .filter((item) => item.group === group)
                .map((item) => (
                  <div
                    key={item.id}
                    id={item.id}
                    className="item"

                    // THIS MAKES THE ITEM DRAGGABLE!!!

                    draggable
                  >
                     // item title
                    {item.value}
                  </div>
                ))}
            </div>
          </div>
        ))}
      </div>
    </>
  );
}



Enter fullscreen mode Exit fullscreen mode

Dnd.scss



.groups {
  display: flex;
  margin: 5px;
  padding: 5px;
  flex-wrap: wrap;


  .group {
    margin: 2px;
    padding: 20px;
    min-height: 16rem;
    background-color: green;

    .title{
      color: white;
      padding: 0;
      margin-top: 0;
    }
  }
}


.item {
  background-color: yellow;
  color: blue;
  margin: 5px;
  padding: 5px;
  border: 2px green;
  cursor: grab;
}


Enter fullscreen mode Exit fullscreen mode

这会创建类似这样的内容:
图片描述

现在我们将添加事件和事件处理程序。请务必阅读代码中的注释,因为那里有详细的解释。我认为这比在代码之外描述所有内容更简单。

提示:在代码沙盒中注释更容易阅读

Dnd.js



import React, { useState } from "react";
import "./Dnd.scss";

export default function Dnd() {
  // Initial groups to drag between
  const groups = ["group1", "group2", "group3", "noDrop"];
  // Initial items to be dragged 
  const initialItems = [
    { id: 1, group: "group1", value: "drag 1" },
    { id: 2, group: "group1", value: "drag 2" },
    { id: 3, group: "group1", value: "drag 3" }
  ];
  // Sets the state of the items. I may add an "add" function later
  // Can be used to add items
  const [items, setItems] = useState(initialItems);
  // Data about a thing's id, origin, and destination
  const [dragData, setDragData] = useState({});
  // Are we hovering over the noDrop div?
  const [noDrop, setNoDrop] = useState("");

  // onDragStart we setDragData.
  // useState instead of e.dataTransfer so we can transfer more data
  const handleDragStart = (e, id, group) => {
    setDragData({ id: id, initialGroup: group });
  };

  // If we enter the noDrop zone the state will be updated
  // Used for styling.
  const handleDragEnter = (e, group) => {
    if (group === "noDrop") {
      setNoDrop("noDrop");
    }
  };

  // DND will not work without this.
  const handleDragOver = (e) => {
    e.preventDefault();
  };

  // setNoDrop to nothing to return styling to normal
  const handleDragLeave = (e) => {
    setNoDrop("");
  };

  // 1. makes copy of items (newItems)
  // 2. changes category of the item to its new group
  // 3. setItem to our NewItems
  const changeCategory = (itemId, group) => {
    const newItems = [...items];
    newItems[itemId - 1].group = group;
    setItems([...newItems]);
  };

  // 1. setNoDrop in case item was dropped in noDrop
  // 2. gets the item id
  // 3. doesn't allow drop in noDrop
  // 4. changeCategory (see above)
  const handleDrop = (e, group) => {
    setNoDrop("");
    const selected = dragData.id;
    if (group === "noDrop") {
      console.log("nuh uh");
    } else {
      changeCategory(selected, group);
    }
  };

  return (
    <>
      <div className="groups">
        {/* iterate over groups */}
        {groups.map((group) => (
          <div
            // change styling if dragging into noDrop zone
            className={`${
              group === "noDrop" && noDrop === "noDrop" ? noDrop : "group"
            }`}
            // event handlers
            onDragEnter={(e) => handleDragEnter(e, group)}
            onDragOver={handleDragOver}
            onDragLeave={handleDragLeave}
            onDrop={(e) => handleDrop(e, group)}
            key={group}
          >
            <h1 className="title">{group}</h1>
            <div>
              {/* iterate over items */}
              {items
                .filter((item) => item.group === group)
                .map((item) => (
                  <div
                    key={item.id}
                    id={item.id}
                    // change style if dragged over noDrop
                    className={`${
                      group === "noDrop" && noDrop === "noDrop"
                        ? "notAllowed"
                        : "item"
                    }`}
                    // MAKES THE ITEM DRAGGABLE!!!!
                    draggable
                    // event handler
                    onDragStart={(e) => handleDragStart(e, item.id, group)}
                  >
                    {/* The name of each item */}
                    {item.value}
                  </div>
                ))}
            </div>
          </div>
        ))}
      </div>
    </>
  );
}


Enter fullscreen mode Exit fullscreen mode

Dnd.scss



.groups {
display: flex;
margin: 5px;
padding: 5px;
flex-wrap: wrap;

.group {
margin: 2px;
padding: 20px;
min-height: 16rem;
background-color: green;

<span class="nc">.title</span><span class="p">{</span>
  <span class="nl">color</span><span class="p">:</span> <span class="no">white</span><span class="p">;</span>
  <span class="nl">padding</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
  <span class="nl">margin-top</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="p">}</span>
Enter fullscreen mode Exit fullscreen mode

}
.noDrop {
margin: 2px;
padding: 20px;
min-height: 16rem;
background-color: red;
cursor: not-allowed !important;

<span class="nc">.title</span><span class="p">{</span>
  <span class="nl">color</span><span class="p">:</span> <span class="no">white</span><span class="p">;</span>
  <span class="nl">padding</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
  <span class="nl">margin-top</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="p">}</span>
Enter fullscreen mode Exit fullscreen mode

}
}

.item {
background-color: yellow;
color: blue;
margin: 5px;
padding: 5px;
border: 2px green;
cursor: grab;
}

.notAllowed {
background-color: yellow;
color: blue;
margin: 5px;
padding: 5px;
border: 2px green;
cursor: not-allowed;
}

Enter fullscreen mode Exit fullscreen mode




这就是它的样子

图片描述

结论

这就是它的基本要点。如果你需要一些简单易用的东西,那就是它了,否则,你可以随意安装一个库。

观看实际操作!查看代码沙盒

文章来源:https://dev.to/wolfmath/drag-and-drop-with-react-519m
PREV
如何使用 React、Spotify 和 Fauna 创建音乐播放列表 Spotify 是什么?Spotify Web API 入门 Fauna 是什么?Fauna DB 入门 构建应用程序 结论
NEXT
Migrating your React app from Webpack to Vite