超详细的实现 React 组件拖拽功能

4,832 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

如果接触过像 Jira, Trello 这种任务处理工具的话,一定对组件拖拽功能不陌生,一般它们的任务面板或者列表等都会支持这项功能,这让用户使用起来会非常方便,那么本文就将基于 React 世界中最流行的拖拽组件库 react-beautiful-dnd 来实现组件的拖拽功能。

前方高能.gif

React 18 实现的坑

因为实现这个项目用的最新版的 React 18,结果发现怎么都没有拖拽的效果,上网查了一下才发现有一些坑,根组件 App 不能被 React.StrictMode 包裹,否则就不起效,下面是实现这个功能的库版本

"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.2.0"

对于这个问题可以参考 github 上这个 issue github.com/atlassian/r…

react-beautiful-dnd 库介绍

react-beautiful-dnd 是目前最流行的 React 拖拽开源库,github 上有28k的 Star 数,通过使用它可以很方便的实现拖拽功能。它的主要特性有:

  • 高度可定制
  • 不需要创建额外的 DOM 节点
  • 提供了一系列的选项和元数据,让使用者可以任意的组合

它提供了三个组件,分别是:

DragDropContext

这个组件是用来包裹 Droppable 的,通过 React 的 Context 机制,它能有所有拖拽组件的信息,它可以接收几个回调函数,必须要实现的是 onDragEnd,后面会详细讲解这个回调的使用。

Droppable

这个组件在 DragDropContext 内部,包裹所有的可拖拽组件,它所定义的区域就是组件可以拖拽的区域。

Draggable

这个组件用来包裹可以拖拽的每个组件的,也就是 Item

这三者的关系用伪代码来说就是:

    <DragDropContext>
       <Droppable>
           {
               ()=> list.map(() => <Draggable/>)
           }
       <Droppable/>
    <DragDropContext/>

开始实现拖拽功能

安装 react-beautiful-dnd 库

npm install react-beautiful-dnd --save

创建 DragAndList 组件

首先,在项目中新建 DragAndList.tsx 组件

// DragAndList.tsx

import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import ListItem from "./ListItem";
import {useState} from "react";

// 用来渲染列表的数据
const elements = [
  { id: "one", content: "我是第一" },
  { id: "two", content: "我是第二" },
  { id: "three", content: "我是第三" },
  { id: "four", content: "我是第四" }
];

// 这个就是拖拽后的回调事件
const onDragEnd = (result: any) => {
  const newItems = Array.from(items);
  const [removed] = newItems.splice(result.source.index, 1);
  newItems.splice(result.destination.index, 0, removed);
  setItems(newItems);
};


// 这里就是拖拽组件了
return <DragDropContext onDragEnd={onDragEnd}>
  <Droppable droppableId="droppable">
    {(provided) => (
        <div {...provided.droppableProps} ref={provided.innerRef}>
          {items.map((item, index) => (
              <Draggable key={item.id} draggableId={item.id} index={index}>
                {(provided, snapshot) => (
                    <ListItem
                        provided={provided}
                        item={item}
                    />
                )}
              </Draggable>
          ))}
          {provided.placeholder}
        </div>
    )}
  </Droppable>
</DragDropContext>

上面的代码有几点需要注意的地方:

  • onDragEnd 回调函数,需要创建后传递给 DragDropContext,因为 react-beautiful-dnd 这个库只负责了动画部分,而不负责处理数据,如果不定义这个回调函数的话,那拖拽的效果是有的,但是拖拽后还是会回到原位,也就是数据没有变,所以这里需要在这个函数中改变数据。
  • Droppable 组件中 droppableId 是必传的,而且需要是唯一的
  • Droppable 组件的 Children 是一个函数,而且必须要返回 React Element 元素
  • ...provided.droppableProps 这些传参,包括后面的 ListItem 中类似的传参,一定要写

创建 ListItem 组件

接下来再创建 ListItem 组件

import {Card} from "antd";

const ListItem = ({item, provided}: any) => {
    return <div
        ref={provided.innerRef}
        {...provided.draggableProps}
        {...provided.dragHandleProps}
    >
        <Card style={{margin: 20}}>
            <p>{item.content}</p>
        </Card>
    </div>
}

export default ListItem

这个组件比较简单,就是定义了每个 Item 而已。

在 App 组件中引入 DragAndList 组件

import DragAndList from "./DragAndList";

function App() {
  return (
    <div style={{backgroundColor: 'lightGrey',width: 500,padding: 20,borderRadius: 5,height: 800}}>
      <DragAndList/>
    </div>
  )
}

export default App

实现的效果如下:

CPT2211212326-540x840.gif

总结

react-beautiful-dnd 实现拖拽功能还是非常灵活的,而且更难得的是它侵入的代码很少,只是一些拖拽的动画实现,可以非常灵活的让使用者来自己定义渲染样式,以及怎样处理数据,并且构建出很强大的可拖拽组件。