@dnd-kit:为 React 打造的一个高性能、轻量级的拖放工具包

@dnd-kit:为 React 打造的一个高性能、轻量级的拖放工具包

@dnd-kit 是一个轻量级、模块化、高性能、可访问和可扩展的 React 拖放工具包(drag & drop toolkit)。使用简单,功能强大。今天就来学习。

先初始化项目

初始化项目

$ npm -v
8.19.2

$ npm create vite@latest dnd-kit-demo -- --template react-ts
Need 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

为了让 useDraggableuseDroppable 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.tsxDroppable.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 元素变成可拖动元素。

释出的 attributeslisteners 属性则可以直接透传给我们的可拖动元素。

释出的 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 与当前放置区域的 iddroppableId === id),相等的话,表示拖动元素被放在了这里,否则就不是。

至此,我们就完成了一个简单的拖放应用。更多详细内容还要参照官方文档[6]及提供的丰富用例[7]

参考链接

  • https://docs.dndkit.com/
  • https://docs.dndkit.com/introduction/getting-started

References

[1]

@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

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!