【JS 第三方库】使用 @dnd-kit/react 实现优雅的拖拽交互

2026-04-28 09:59:51

目录:

@dnd-kit/react 介绍

我过去实现拖拽功能时一直使用原生 JavaScript,虽然之前了解过 @dnd-kit/core,但总觉得它过于复杂。最近发现社区推出了一个新工具——@dnd-kit/react,尝试之后发现,在 React 项目中用它来实现窗口拖拽或元素排序简直太方便了。

安装依赖:

$ npm install @dnd-kit/react

创建可拖动元素

useDraggable 钩子用于创建一个可拖动的元素。

import { useDraggable } from '@dnd-kit/react';

function Draggable() {
  const { ref } = useDraggable({
    id: 'draggable',
  })

  return (
    <div ref={ref}>
      Draggable
    </div>
  )
}

你可能会问:给元素直接设置 draggable=”true” 属性不也能拖吗?两者有何区别?

实际上,draggable=”true” 主要是为浏览器原生的文件拖放和文本拖动而设计,拖动时原始元素会留在原地,光标处显示的是一个半透明的“副本”。
而 useDraggable 则提供了更贴近交互设计的能力,比如它可以让你“拽着”元素本身移动,并轻松实现单方向拖动、限制边界、添加弹性动画等高级效果。

创建可放置区域

与拖动对应的是放置。useDroppable 钩子用于创建一个可以接收被拖动元素的区域。

import { useDroppable } from '@dnd-kit/react';

function Droppable({ id, children }) {
  const { ref } = useDroppable({
    id,
  })

  return (
    <div ref={ref}>
      {children}
    </div>
  )
}

可拖动元素与可放置区域完整示例

将两者结合,可以构建一个简单的拖放应用:

import React, { useState } from 'react'
import { DragDropProvider, useDraggable, useDroppable } from '@dnd-kit/react'

function Draggable() {
  const { ref } = useDraggable({ id: 'draggable' })
  return <div ref={ref}>Draggable</div>
}

function Droppable({ id, children }) {
  const { ref } = useDroppable({ id })
  return <div ref={ref}>{children}</div>
}

function App() {
  const [isDropped, setIsDropped] = useState(false)

  return (
    <DragDropProvider
      onDragEnd={(event) => {
        if (event.canceled) return;
        const { target } = event.operation;
        setIsDropped(target?.id === 'droppable');
      }}
    >
      {!isDropped && <Draggable />}
      <Droppable id="droppable">
        {isDropped && <span>已放入</span>}
      </Droppable>
    </DragDropProvider>
  )
}

export default App

使用 handleRef 指定拖动把手

开发时经常遇到如下场景: 你不想让整个卡片都能被拖动,而只希望用户通过点击卡片角落的“把手”(如六个点图标)来触发拖动。这时,handleRef 就能派上用场。

如下示例: 点击 “Draggable” 文本是不会拖动的,只有点击 “只有点我才能拖动” 才会拖动。

function Draggable() {
  const { ref, handleRef } = useDraggable({
    id: 'draggable',
  })

  return (
    <div ref={ref}>
      Draggable
      <span ref={handleRef} style={{ color: 'red' }}>
        只有点我才能拖动
      </span>
    </div>
  )
}

使用 modifiers 限制拖动行为

modifiers 让你能精细地控制拖动的物理行为,例如限制只能在水平或垂直方向移动,或禁止元素被拖出某个容器的边界:

import { useDraggable } from '@dnd-kit/react';
import { RestrictToElement } from '@dnd-kit/dom/modifiers';

function Draggable({ id }) {
  const { ref } = useDraggable({
    id,
    modifiers: [RestrictToElement.configure({ element: document.body })], // 限制容器拖出 document.body 边界
  })
}

使用 isDropTarget 实现视觉反馈

useDroppable 返回的 isDropTarget 是一个布尔值,用于指示当前是否有可拖曳元素拖到了可放置区域上方,通常会加样式提升交互反馈。

如下示例: 当可拖曳元素拖到可放置区域上方,此时还没松开鼠标,给可放置区域添加 ‘active-droppable’ 类名,比如加个边框。

function Droppable({ id, children }) {
  const { ref, isDropTarget } = useDroppable({ id });

  return (
    <div
      ref={ref}
      className={isDropTarget ? 'active-droppable' : 'droppable'}
    >
      {children}
    </div>
  )
}

实现拖拽排序

排序是拖拽的常见场景。如果使用传统的 @dnd-kit/core 来实现,还需要额外安装 @dnd-kit/sortable 库,使用 @dnd-kit/react 是不需要额外安装库的,自身已经携带了排序功能。

import { useState } from 'react';
import { DragDropProvider } from '@dnd-kit/react';
import { useSortable } from '@dnd-kit/react/sortable';
import { move } from '@dnd-kit/helpers';

function SortableItem({ id, index }: { id: string, index: number }) {
  const { ref, isDragging } = useSortable({ id, index });

  return (
    <div
      ref={ref}
      style={{ opacity: isDragging ? 0.5 : 1 }}
    >
      项目 {id}
    </div>
  )
}

export function App() {
  const [items, setItems] = useState([
    { id: '1', name: '项目 1' },
    { id: '2', name: '项目 2' },
    { id: '3', name: '项目 3' },
    { id: '4', name: '项目 4' },
    { id: '5', name: '项目 5' },
  ])
  console.log('items', items)

  return (
    <DragDropProvider
      onDragEnd={(event) => {
        setItems((currentItems) => move(currentItems, event));
      }}
    >
      <div>
        {items.map((item, index) => (
          <SortableItem key={item.id} id={item.id} index={index} />
        ))}
      </div>
    </DragDropProvider>
  );
}

export default App

返回首页

本文总阅读量  次
皖ICP备17026209号-3
总访问量: 
总访客量: