在 React 中拖放

Avatar of Maks Akymenko
Maks Akymenko

DigitalOcean 为您的旅程的每个阶段提供云产品。 立即开始使用 200 美元的免费信用额度!

React 生态系统为我们提供了许多库,它们都专注于拖放交互。 我们有 react-dndreact-beautiful-dndreact-drag-n-drop 等等,但其中一些需要相当多的工作才能构建一个简单的拖放演示,而有些则没有提供更复杂的功能(例如多个拖放实例),如果有,它会变得非常复杂。

这就是 react-sortable-hoc 发挥作用的地方。

💡 本教程需要您具备 React 库和 React 钩子的基本知识。

该库在其名称中包含“HOC”是有充分理由的。 它提供 高阶组件,这些组件将拖放功能扩展到组件。

让我们一起了解其功能的实现。

启动项目

在本教程中,我们将构建一个包含有趣 GIF(来自 Chris Gannon!)的应用程序,这些 GIF 可以拖动到视窗周围。

GitHub 仓库

让我们创建一个简单的应用程序,并为其添加拖放功能。 我们将使用 create-react-app 来启动一个新的 React 项目

npx create-react-app your-project-name

现在,让我们切换到项目目录并安装 react-sorting-hoc 和 array-move。 后者是用来在拖动后将数组中的项目移动到不同位置所需的。

cd your-project-name
yarn add react-sortable-hoc array-move

添加样式、数据和 GIF 组件

为了简单起见,我们将把所有样式都写入我们的 App.css 文件中。 您可以用以下样式覆盖您在那里的样式

.App {
  background: #1a1919;
  color: #fff;
  min-height: 100vh;
  padding: 25px;
  text-align: center;
}

.App h1 {
  font-size: 52px;
  margin: 0;
}

.App h2 {
  color: #f6c945;
  text-transform: uppercase;
}

.App img {
  cursor: grab;
  height: 180px;
  width: 240px;
}

让我们使用 React 内置的 useState 钩子创建包含 GIF 的状态

import React, { useState } from 'react';

现在,在 return 语句之前添加以下内容

const [gifs, setGifs] = useState([
  'https://media.giphy.com/media/3ohhwoWSCtJzznXbuo/giphy.gif',
  'https://media.giphy.com/media/l46CbZ7KWEhN1oci4/giphy.gif',
  'https://media.giphy.com/media/3ohzgD1wRxpvpkDCSI/giphy.gif',
  'https://media.giphy.com/media/xT1XGYy9NPhWRPp4pq/giphy.gif',
]);

现在,在 src 目录中创建一个 Gif.js 文件,并传入以下代码

import React from 'react';
import PropTypes from 'prop-types';

const Gif = ({ gif }) => (<img src={gif} alt="gif" />)

Gif.propTypes = {
  gif: PropTypes.string.isRequired,
};

export default Gif;

我们始终努力在编写代码时遵循最佳实践;因此,我们还为类型检查导入了 PropTypes

导入 Gif 组件,并将其添加到主 App 组件中。 经过一些整理,它看起来像这样

import React, { useState } from 'react';
import './App.css';

import Gif from './Gif';

const App = () => {
  const [gifs, setGifs] = useState([
    'https://media.giphy.com/media/3ohhwoWSCtJzznXbuo/giphy.gif',
    'https://media.giphy.com/media/l46CbZ7KWEhN1oci4/giphy.gif',
    'https://media.giphy.com/media/3ohzgD1wRxpvpkDCSI/giphy.gif',
    'https://media.giphy.com/media/xT1XGYy9NPhWRPp4pq/giphy.gif',
  ]);


  return (
    <div className="App">
      <h1>Drag those GIFs around</h1>
      <h2>Set 1</h2>
        {gifs.map((gif,  i) => <Gif key={gif} gif={gif} />)}
    </div>
  );
}

export default App;

转到 http://localhost:3000/ 以查看应用程序现在的外观

react-sortable-hoc-article-app 的屏幕截图

进入拖放部分

好了,现在该让我们的 GIF 可拖动了! 以及可放置。

首先,我们需要从 react-sortable-hoc 中获取两个 HOC,以及来自 array-move 库的 arrayMove 方法,以便在拖动发生后修改我们的新数组。 我们希望我们的 GIF 保持在新的位置,对吧? 好吧,这就是它将允许我们做的事情。

让我们导入它们

import { sortableContainer, sortableElement } from 'react-sortable-hoc';
import arrayMove from 'array-move';

您可能已经猜到,这些组件将是包装器,它将公开我们需要的功能。

  • sortableContainer 是可排序元素的容器。
  • sortableElement 是我们正在渲染的每个单个元素的容器。

让我们在所有导入之后执行以下操作

const SortableGifsContainer = sortableContainer(({ children }) => <div className="gifs">{children}</div>);
    
const SortableGif = sortableElement(({ gif }) => <Gif key={gif} gif={gif} />);

我们刚刚为要传递到 SortableGifsContainer 中的子元素创建了一个容器,还为单个 Gif 组件创建了一个包装器。
如果您不太清楚,别担心——您在实现它之后就会明白。

💡注意:您需要将子元素包装在 div 或任何其他有效的 HTML 元素中。

现在该将我们的 GIF 包装到 SortableGifsContainer 中,并将 Gif 组件替换为我们新创建的 SortableGif

<SortableGifsContainer axis="x" onSortEnd={onSortEnd}>
  {gifs.map((gif, i) =>
    <SortableGif
    // don't forget to pass index prop with item index
      index={i}
      key={gif}
      gif={gif}
    />
  )}
</SortableGifsContainer>

重要的是要注意,您需要将 index 属性传递给您的可排序元素,以便库可以区分项目。 这类似于在 React 中 向列表添加键)。

我们添加了 axis,因为我们的项目是水平排列的,我们希望水平拖动它们,而默认情况下是垂直拖动。 换句话说,我们限制了沿着水平 x 轴的拖动。 正如您所看到的,我们还添加了一个 onSortEnd 函数,该函数会在我们拖动或排序项目时触发。 当然,还有很多其他事件,但您可以在 文档 中找到更多信息,该文档已经出色地涵盖了这些事件。

现在该实现它了! 在 return 语句之前添加以下行

const onSortEnd = ({ oldIndex, newIndex }) => setGifs(arrayMove(gifs, oldIndex, newIndex));

我想再解释一件事:我们的函数收到了被拖动的项目的旧索引和新索引,当然,每次我们在移动项目后,我们都会借助 arrayMove 来修改我们最初的数组。

太棒了! 现在您知道如何在项目中实现拖放了。 现在就开始行动吧! 🎉 🎉 🎉

如果我们有多个项目列表怎么办?

如您所见,前面的示例相对简单。 您基本上将每个项目包装在一个可排序的 HOC 中,然后将其包装在 sortableContainer 中,这样,您就拥有了基本的拖放功能。

但是,我们如何用多个列表来做到这一点呢? 好消息是 react-sortable-hoc 为我们提供了一个 collection 属性,以便我们可以区分列表。

首先,我们应该添加第二个 GIF 数组

const [newGifs, setNewGifs] = useState([
  'https://media.giphy.com/media/xiOgHgY2ceKhm46cAj/giphy.gif',
  'https://media.giphy.com/media/3oKIPuMqYfRsyJTWfu/giphy.gif',
  'https://media.giphy.com/media/4ZgLPakqTajjVFOVqw/giphy.gif',
  'https://media.giphy.com/media/3o7btXIelzs8nBnznG/giphy.gif',
]);

如果您想在继续之前查看它们,请在 SortableGifsContainer 结束标签之后添加以下行

{newGifs.map(gif => <Gif key={gif} gif={gif} />)}

好了,现在该用可拖动版本替换它了。

除了一个地方之外,实现与第一个示例相同——我们已将 collection 属性添加到 SortableGif 中。 当然,您可以为该集合想出一个任何名称,只需记住,我们需要在 onSortEnd 函数中使用它。

<h2>Set 2</h2>

<SortableGifsContainer axis="x" onSortEnd={onSortEnd}>
  {newGifs.map((gif,  i) => <SortableGif index={i} key={gif} gif={gif} collection="newGifs" />)}
</SortableGifsContainer>

接下来,我们需要将 collection 属性添加到我们的第一个列表中。 我选择将第一个项目列表的名称命名为 GIF,但这由您决定!

现在,我们需要更改我们的 onSortEnd 函数。 我们的函数收到了旧索引和新索引,但我们也可以从中解构出一个集合。 没错,就是我们在 SortableGif 中添加的那个。

所以我们现在要做的就是编写一个 JavaScript switch 语句来检查集合名称,并在拖动时修改正确的 GIF 数组。

const onSortEnd = ({ oldIndex, newIndex, collection }) => {
  switch(collection) {
    case 'gifs':
      setGifs(arrayMove(gifs, oldIndex, newIndex))
      break;
    case 'newGifs':
      setNewGifs(arrayMove(newGifs, oldIndex, newIndex))
      break;
    default:
      break;
  }
}

现在该检查一下了!

如您所见,我们现在有两个独立的 GIF 列表,我们可以拖动和排序它们。 此外,它们是独立的,这意味着来自不同列表的项目不会混合在一起。

这正是我们想要做的事情! 现在您知道如何创建和处理多个项目列表的拖放功能了。 祝贺您 🎉

希望您和我在编写时一样享受它! 如果您想参考完整的代码,它 已在 GitHub 上发布。 如果您有任何问题,请随时 通过电子邮件与我联系