@dnd-kit 是一个轻量级、模块化、高性能、可访问和可扩展的 React 拖放工具包(drag & drop toolkit)。使用简单,功能强大。今天就来学习。
先初始化项目。
初始化项目
$ npm -v
8.19.2
$ npm create vite@latest dnd-kit-demo -- --template react-tsNeed to install the following packages:
create-vite@4.4.1
Ok to proceed? (y) y
Scaffolding project in D:fe-projectsdnd-kit-demo...
Done. Now run:
cd dnd-kit-demo
npm install
npm run dev
按照指示启动项目:
$ cd dnd-kit-demo
# open use VS Code
$ code .
$ npm install
$ npm run dev
VITE v4.4.11 ready in 229 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h to show help
然后安装 dnd kit 依赖。
安装 dnd kit 依赖
$ npm install @dnd-kit/core
dnd-kit 分成多个子包发布,@dnd-kit/core
为其核心包,提供了构建一个完整拖拽应用的所有 API。限于篇幅,本文只涉及核心包 API 的使用。
❝
dnd-kit 还针对排序场景创建了 @dnd-kit/sortable[1](核心包之上的一层薄薄地封装)。有兴趣的同学可以私下学习,还是蛮有用的。
❞
快速上手
下面我们将构建一个简单的拖拽应用来学习 dnd kit 所涉及到的核心概念,掌握基本使用。
核心概念
dnd kit 的核心库包含两个主要概念:可拖动元素(Draggable elements)[2]以及可放置区域(Droppable areas)[3]。
而通过 useDraggable[4] 和 useDroppable[5] 两个自定义 Hook,就能拓展我们的组件能力,创建支持拖放的组件。
引入 Context provider
为了让 useDraggable
和 useDroppable
Hook 能正常工作,需要确保使用它们的组件被封装在 <DndContext />
中:
import React from 'react';
import {DndContext} from '@dnd-kit/core';
import {Draggable} from './Draggable';
import {Droppable} from './Droppable';
function App() {
return (
<DndContext>
<Draggable>Draggable Elem</Draggable>
<Droppable>Droppable Area</Droppable>
</DndContext>
)
}
Draggable.tsx
和 Droppable.tsx
是接下来我们要编写的组件。
创建 Droppable 组件
创建可放置组件,需要使用 useDroppable
Hook。
我们先看代码是怎么写的:
import React from 'react';
import {useDroppable} from '@dnd-kit/core';
function Droppable(props) {
const {isOver, setNodeRef} = useDroppable({
id: 'droppable',
});
const style = {
color: isOver ? 'red' : 'black',
padding: '0.5rem',
borderRadius: '0.5rem',
border: '1px solid currentColor',
}
return (
<div ref={setNodeRef} style={style}>
{props.children}
</div>
);
}
useDroppable
Hook 释出的 setNodeRef
用于以 ref
形式与项目中的某个 DOM 元素进行绑定,这个 DOM 元素就成为可放置区域了。
释出的布尔属性 isOver
,则会在可放置的区域被可拖放元素悬停时变成 true
,其余情况下则为 false
。
当然,还需要为可放置组件提供唯一的 id
属性,用于后续 dnd kit 的操作。
创建 Draggable 组件
创建可拖动组件,需要使用 useDraggable
Hook。
我们先看代码是怎么写的:
import React from 'react';
import {useDraggable} from '@dnd-kit/core';
function Draggable(props) {
const {attributes, listeners, setNodeRef, transform} = useDraggable({
id: 'draggable',
});
const style = transform ? {
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
} : undefined;
return (
<button ref={setNodeRef} style={style} {...listeners} {...attributes}>
{props.children}
</button>
);
}
比可放置组件多一些,不过没关系,我们一个个来看。
与 useDroppable
Hook 一样,useDraggable
Hook 也释出了 setNodeRef
用于以 ref
形式将某个 DOM 元素变成可拖动元素。
释出的 attributes
、listeners
属性则可以直接透传给我们的可拖动元素。
释出的 transform
属性则表示拖动元素的位置偏移,我们可以利用这个参数同步设置元素的偏移效果,不然元素是不会动的(在本例中就是用来给拖动元素设置 CSS transform
属性,可谓天衣无缝)
当然,还需要为可拖动组件提供唯一的 id
属性,用于后续 dnd kit 的操作。
@dnd-kit/utilities
可拖动组件的位置偏移代码,组装起来实在有点麻烦,因此 dnd kit 还提供了一个工具包,方便我们的一些日常操作,比如这里偏移计算。
首先安装依赖:
npm install @dnd-kit/utilities
然后替换写法:
// 之前
const style = transform ? {
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
} : undefined;
// 之后
import {CSS} from '@dnd-kit/utilities';
// Within your component that receives `transform` from `useDraggable`:
const style = {
transform: CSS.Translate.toString(transform),
}
完善功能
到目前位置,我们已经创建好了一个可拖动组件和可放置组件。但还无法实现讲可拖动组件放入可放置组件内部。
如何做呢?
这就需要我们在 <DndContext>
上设置监听事件,监听可拖动组件是否位于可放置组件区域范围内。
import React, {useState} from 'react';
import {DndContext} from '@dnd-kit/core';
import {Droppable} from './Droppable';
import {Draggable} from './Draggable';
function App() {
const [isDropped, setIsDropped] = useState(false);
const draggableMarkup = (
<Draggable>Drag me</Draggable>
);
return (
<DndContext onDragEnd={handleDragEnd}>
{isDropped ? null : draggableMarkup }
<Droppable>
{isDropped ? draggableMarkup : 'Drop here'}
</Droppable>
</DndContext>
);
function handleDragEnd(event) {
if (event.over && event.over.id === 'droppable') {
setIsDropped(true);
} else {
setIsDropped(false)
}
}
}
我们在 <DndContext>
上注册了 onDragEnd
事件,所有在 <DndContext>
范围内发生的停止拖拽事件都会被捕获,交给 handleDragEnd
函数处理。
处理函数 handleDragEnd
接收的参数 event
中包含一个 over
属性——当在停止拖拽时,可放置区域内有可拖放元素悬停时,over
属性就对应可拖放元素悬停时所在的那个可放置区域,也就是本例中的 <Droppable>
元素。
因此,我们设置了一个状态变量 isDropped
保存放置状态。如果拖动结束时,拖动元素元素正在在可放置区域,我们就在 <Draggable>
放在 <Droppable>
里面,否则放外面。
多个可放置区域
上述我们只实现了一个可放置区域的拖动效果。对于更复杂的场景,我们还能支持多个可放置区域。
首先,需要改造下 <Droppable>
,支持接受 id
prop,用于支持多可放置区域的设置。
export function Droppable(props: any) {
const { isOver, setNodeRef } = useDroppable({
- id: 'droppable',
+ id: props.id,
})
// ...
}
接下里,改造 <App>
,同时渲染 A、B、C 3 个可放置区域。
import {useState} from 'react';
import {DndContext} from '@dnd-kit/core';
import {Draggable} from './Draggable';
import {Droppable} from './Droppable';
function App() {
const containers = ['A', 'B', 'C'];
const [droppableId, setdDroppableId ] = useState(null);
function handleDragEnd(event: any) {
if (event.over && event.over.id) {
setdDroppableId(event.over.id)
} else {
setdDroppableId(null)
}
}
const draggableMarkup = (
<Draggable>Drag me</Draggable>
);
return (
<DndContext onDragEnd={handleDragEnd}>
{droppableId ? null : draggableMarkup }
{containers.map((id) => {
return <Droppable key={id} id={id}>
{droppableId === id ? draggableMarkup : 'Drop here'}
</Droppable>
})}
</DndContext>
)
}
export default App
我们将之前的布尔状态变量 isDropped
替换成记录 droppable id 的状态变量 droppableId
。然后,在渲染多个可放置区域的地方,比较 droppableId
与当前放置区域的 id
(droppableId === id
),相等的话,表示拖动元素被放在了这里,否则就不是。
至此,我们就完成了一个简单的拖放应用。更多详细内容还要参照官方文档[6]及提供的丰富用例[7]。
参考链接
-
https://docs.dndkit.com/ -
https://docs.dndkit.com/introduction/getting-started
References
@dnd-kit/sortable: https://docs.dndkit.com/presets/sortable
[2]可拖动元素(Draggable elements): https://docs.dndkit.com/api-documentation/draggable
[3]可放置区域(Droppable areas): https://docs.dndkit.com/api-documentation/droppable
[4]useDraggable: https://docs.dndkit.com/introduction/getting-started
[5]useDroppable: https://docs.dndkit.com/introduction/getting-started
[6]官方文档: https://docs.dndkit.com/
[7]丰富的用例: https://master–5fc05e08a4a65d0021ae0bf2.chromatic.com/
原文始发于微信公众号(写代码的宝哥):@dnd-kit:为 React 打造的一个高性能、轻量级的拖放工具包
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/243811.html