翻译:https://css-tricks.com/draggi…
React 社区提供了许多的库来实现拖放的功能,例如 react-dnd, react-beautiful-dnd, react-drag-n-drop,等等。但是它们有一些共同的缺陷:
- 使用复杂,有时候需要做很多工作才能构建一个简单的拖放演示;
- 功能有限,例如无法实现多个拖放实例这样复杂的功能,如果有的话,它也会变得非常复杂。
为了解决这些问题,react-sortable-hoc 应运而生。
这篇教程需要你提前了解一些 React 组件以及 hooks 的基础知识。
React 官方推荐的封装高阶组件的方式是 HOC,我们需要借助它来实现一个具有拖放功能的高阶组件。
HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
启动项目
教程最终的目的是构建一个带有趣 GIF 的应用程序(来自 Chris Gannon!),可以在视口周围拖动。具体见
首先,我们利用 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 hook 来实现 GIF 组件
import React, {useState} from 'react';
在 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;
运行 npm run start
,访问 http://localhost:3000/,可以看到如下结果
添加拖放功能
现在让我们对 Gif 组件添加拖拽功能。首先,我们需要了解 react-sortable-hoc 的两个 HOC,以及 array-move 的 arrayMove
方法,以便于在拖动发生修改数组。首先引入对应组件以及方法
import {sortableContainer, sortableElement} from 'react-sortable-hoc';
import arrayMove from 'array-move';
之前提到过 HOC,本质上 高阶组件是参数为组件,返回值为新组件的函数。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
可以看出,HOC 就是在当前 component 的外层包裹我们所需要实现的功能。所以 sortableContainer
,sortableElement
就是 higherOrderComponent
。
-
sortableContainer
是所有可排序元素的容器; -
sortableElement
是每个可渲染元素的容器。
import 之后我们要做的是
const SortableGifsContainer = sortableContainer(({children}) => <div className="gifs">{children}</div>);
const SortableGif = sortableElement(({gif}) => <Gif key={gif} gif={gif} />);
SortableGif
为每个子元素创建了一个容器,也就是为单个 Gif 组件创建。它们将使用在 SortableGifsContainer
中,以 children
属性传递进去
注:您需要将每个子项在 div 或任何其他有效的 HTML 元素中。
然后用新创建的 SortableGif
替换原有的 Gif
组件,并在 SortableGifsContainer
中使用。
<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 prop 传递给可排序元素,以便库可以区分每一个子项目。它类似于在 React 中向列表添加键。
在 SortableGifsContainer
上,我们添加了 axis="x"
,是因为 Gif 组件是水平放置的,如果想要水平拖动它们就需要配置 axis
,而默认是垂直拖动。换句话说,axis="x"
限制子项目只能沿水平 x 轴拖放。同时,还添加了 onSortEnd={onSortEnd}
,该函数在每次拖动或排序项目时触发。它的实现如下
const onSortEnd = ({oldIndex, newIndex}) => setGifs(arrayMove(gifs, oldIndex, newIndex));
onSortEnd
接收了一个被拖动的项目的新旧 index
,当然,每次我们移动项目后,我们都会在 arrayMove 的帮助下修改数据。
现在已经知道如何在项目中实现拖放!????????????
如果我们有多个项目列表怎么办?
之前的例子是为了展示 react-sortable-hoc
的功能,所以非常的清晰以及简单。但是在实际的业务中,你可能会遇到多个项目列表都需要拖放的场景,那该怎么办?
react-sortable-hoc 为我们提供了一个 collection
prop 用来区分列表。在上一个例子的基础上,我们添加一个新的列表
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',
]);
和之前一样,除了需要在 SortableGif
组件上添加 collection
props 具体如下
<h2>Set 2</h2>
<SortableGifsContainer axis="x" onSortEnd={onSortEnd}>
{newGifs.map((gif, i) => (
<SortableGif
index={i}
key={gif}
gif={gif}
collection="newGifs"
/>
))}
</SortableGifsContainer>
在之前的第一个列表中也添加相应的 collection
属性,例如 collection="gifs"
之后修改 onSortEnd
函数,它的参数中会多一个 collection
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 列表,可以自由拖放。而且,它们是独立的意思,来自不同列表的项目不会混淆。