React 生态系统为我们提供了许多库,它们都专注于拖放交互。 我们有 react-dnd、react-beautiful-dnd、react-drag-n-drop 等等,但其中一些需要相当多的工作才能构建一个简单的拖放演示,而有些则没有提供更复杂的功能(例如多个拖放实例),如果有,它会变得非常复杂。
这就是 react-sortable-hoc 发挥作用的地方。
💡 本教程需要您具备 React 库和 React 钩子的基本知识。
该库在其名称中包含“HOC”是有充分理由的。 它提供 高阶组件,这些组件将拖放功能扩展到组件。
让我们一起了解其功能的实现。
启动项目
在本教程中,我们将构建一个包含有趣 GIF(来自 Chris Gannon!)的应用程序,这些 GIF 可以拖动到视窗周围。
让我们创建一个简单的应用程序,并为其添加拖放功能。 我们将使用 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/ 以查看应用程序现在的外观

进入拖放部分
好了,现在该让我们的 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 上发布。 如果您有任何问题,请随时 通过电子邮件与我联系。
在 2019 年编写 HOC 应该是一种犯罪!
在 Firefox 中无法使用。
我在 Firefox 中也看到了一些非常怪异的行为。 希望 Maks 能解决它。
是的,伙计们! 感谢您指出这一点! 我找到了一种解决方案:基本上,您需要做的就是向 SortableContainer 添加一个
onSortStart
,并阻止默认事件。 这里有一个问题:https://github.com/clauderic/react-sortable-hoc/issues/253,以及我已经添加的解决方案:https://github.com/maximakymenko/react-sortable-hoc-article-app/blob/master/src/App.js#L47我刚刚开始使用 React,但想知道是否有一种方法可以将 GIF 从集合 2 拖放到集合 1 中? 比如替换位置?
嘿,Stephen! 非常好的问题。 我为您做了一些调查,似乎甚至有一个 PR 用于添加此功能,但有些事情没有成功。 不过,有一位用户在评论中发布了一个代码片段,他是如何实现的,并且它可以正常使用。 这里有一个链接:https://github.com/clauderic/react-sortable-hoc/pull/138#issuecomment-509514762