【译】dnd-kit 文档中文翻译 -Overview & Quick start

4,485 阅读13分钟

本篇主要是 github 仓库 README.md 与官方文档 中 Overview InstallationQuick start 的对照翻译

翻译主要使用 DeepL 翻译,再根据自己的理解进行修改,如有错误,欢迎指正。 部分名词不会翻译。

  • 功能丰富: 可定制的碰撞检测算法、多个激活器、可拖动的覆盖层、拖动手柄、自动滚动、约束等等。
  • **为 React 构建:**暴露了诸如useDraggableuseDroppable的 hooks 。并且不会迫使你重新架构你的应用程序或创建额外的包装 DOM 节点。
  • 支持广泛的用例: 列表、网格、多个容器、嵌套上下文、可变大小的项目、虚拟化列表、2D 游戏等。
  • 零依赖性和模块化: 库的核心重量约为 10kb ,并且没有外部依赖。它是围绕着内置的 React 状态管理和上下文建立的,这使库保持精简。
  • **内置支持多种输入方法:**指针、鼠标、触摸和键盘传感器。
  • 完全可定制和可扩展: 定制每一个细节:动画、过渡、行为、风格。建立你自己的传感器,碰撞检测算法,自定义键绑定和更多。
  • 可访问性: 支持键盘,合理的默认 aria 属性,可定制的屏幕阅读器指示和内置的实时区域。
  • 性能: 它是在考虑到性能的情况下建立的,以便支持丝般顺滑的动画。
  • 预设: 需要建立一个可排序的界面?看看@dnd-kit/sortable,它是建立在@dnd-kit/core之上的一个薄层。未来会有更多的预设。

文档 Documentation

要了解如何开始使用 dnd kit,请访问官方文档网站。你会发现深入的 API 文档、提示和指南,以帮助你构建拖放界面。

关键概念 Key concepts

dnd kit 的核心库暴露了两个主要概念。

使用 useDraggableuseDroppable hooks 来增强你现有的组件,或者结合这两个 hooks 来创建既可以拖动又可以投放的组件。

使用<DndContext> provider 处理事件并定制你的可拖动元素和可拖动区域的行为。配置传感器以处理不同的输入方法。

使用 <DragOverlay> 组件渲染一个可拖动的覆盖层,该覆盖层从正常文档流中移除,并相对于视口定位。

请查看我们的快速入门指南,了解如何开始。

Extensibility 可扩展性

可扩展性是dnd kit的核心。它是为了精简和可扩展而建立的。它提供了我们认为大多数人在大多数时候需要的功能,并提供了扩展点,以便在 @dnd-kit/core 的基础上建立其他功能。

dnd kit的可扩展性水平的一个典型例子是Sortable preset,它是使用@dnd-kit/core所暴露的扩展点建立的。

dnd kit的主要扩展点是:

  • Sensors 传感器
  • Modifiers 修改器
  • Constraints 限制条件
  • Custom collision detection algorithms 自定义碰撞检测算法

Accessibility 可访问性

构建可访问的拖放界面是很难的;dnd kit 有一些合理的默认值和起点,以帮助你使你的拖放界面具有可访问性。

  • 可定制的 屏幕阅读器说明 如何与可拖动的项目互动

  • 可定制的 实时区域更新 ,为屏幕阅读器提供实时公告,说明当前 draggable 元素和 droppable 元素的情况。

  • 传递给可拖动项目的 aria 属性 的合理默认值

请查看我们的可访问性指南,了解更多关于如何帮助为屏幕阅读器提供更好的体验。

Architecture 架构

与大多数拖放库不同,dnd kit 有意不在HTML5 拖放 API的基础上构建。这是一个深思熟虑的架构决定,在决定使用它之前,你应该意识到其中的利弊,但对于大多数应用而言,我们认为其好处大于利弊。

HTML5 拖放 API 有一些严重的 限制。它不支持触摸设备或使用键盘拖动项目,这意味着建立在它之上的库需要暴露一个完全不同的实现来支持这些输入方法。它也不支持常见的用例,例如将拖动锁定在一个特定的轴或容器的边界,自定义碰撞检测策略,甚至是自定义拖动项目的预览。

虽然有一些解决这些问题的方法,但这些方法通常会增加代码库的复杂性和库的整体大小,并导致鼠标、触摸和键盘层之间的不一致,因为它们是由完全不同的实现提供支持。

不使用 HTML5 拖放 API 的主要弊端是,你无法从桌面或窗口之间进行拖放。如果您所考虑的拖放用例涉及此类功能,您应该使用建立在 HTML 5 Drag and drop API 之上的库。我们强烈建议你查看 react-dnd,它是一个具有本地 HTML 5 拖放后端的 React 库。

Performance 性能

Minimizing DOM mutations 最大限度地减少 DOM 变化

dnd kit lets you build drag and drop interfaces without having to mutate the DOM every time an item needs to shift position.

使用 dnd kit 建立的拖放界面不需要在每次拖拽项变换位置时改变 DOM。

这是因为 dnd kit 在启动拖动操作时,会懒计算并存储可拖动容器的初始位置和布局。这些位置被传递给使用useDraggableuseDroppable 的组件,这样就可以在拖动操作进行时计算拖拽项的新位置,并使用不触发重绘的高性能 CSS 属性(如translate3dscale)将它们移动到新位置。关于如何实现的例子,请看@dnd-kit/sortable 库中的排序策略的实现。

这并不是说你不能在拖动时变换项目在 DOM 中的位置,这是支持的,有时是不可避免的。在某些情况下,我们不可能在移动项目前知道它的新位置和布局。要知道,在拖动时对 DOM 的这种变动是非常昂贵的,并且会导致重绘,所以如果可能的话,最好使用 translate3dscale 来计算新的位置。

Synthetic events 合成事件

dnd kit还为所有传感器的激活器事件使用了SyntheticEvent listeners,这相对于手动添加事件监听器到每个单独的可拖动节点的性能更高。

@dnd-kit仓库中工作 Working in the @dnd-kit repository

这个软件库中包含的包 Packages contained within this repository

  • @dnd-kit/core
  • @dnd-kit/accessibility
  • @dnd-kit/sortable
  • @dnd-kit/modifiers
  • @dnd-kit/utilities

Installing dependencies 安装依赖

你需要在根目录下安装所有的依赖项。由于@dnd-kit是一个使用 「Lerna」 和 「Yarn Workspaces」 的 「monorepo」,不支持 「npm CLI」(只支持 yarn)。

yarn install

这将在每个项目中安装所有的依赖项,构建它们,并通过 Lerna 进行符号连接

Development workflow 开发工作流程

In one terminal, run yarn start in parallel:

yarn start

这将构建每个软件包到 <packages>/<package>/dist,并在 watch 模式下运行项目,这样你在<packages>/<package>/src 中的任何编辑都会导致重建到 <packages>/<package>/dist。结果将流向 terminal。

Running storybook

yarn start:storybook

Open http://localhost:6006 to view it in the browser.

Working with the playground

你可以在由 「Parcel」 支持的 playground 上运行本地包。

yarn start:playground

这将在 localhost:1234 上启动 playground。如果你使用「lerna」在一个终端上以并行模式运行 watch,之后运行「parcel」,当你对任何导入的模块(其源代码在packages/*/src/*内)进行修改时,你的 playground 将热重新加载。请注意,为了实现这一点,每个包的start命令都会给 「TDSX」 传递--noClean标志。这可以防止 「Parcel」 在重建过程中因为文件未找到而报错。

重要的安全提示:当在 playground 中添加/更改包时,在 package.json 中使用 alias 对象。这将告诉 「Parcel」 将它们解析到文件系统中,而不是试图从 NPM 安装包。它还可以修复你可能遇到的重复的 React 错误。

Running Cypress

(在第三个终端中)你可以运行 Cypress,它将针对 storybook 运行集成测试。

Installing 安装

要开始使用@dnd-kit,请通过 「npm」 或 「yarn」 安装核心库。

npm install @dnd-kit/core

你还需要确保你已经安装了 peer dependencies。你有可能已经在你的项目中安装了 「react」 和 「react-dom」,但如果没有,请确保安装它们。

npm install react react-dom

Packages

@dnd-kit 是一个 monorepo。根据你的需要,你可能还想安装 @dnd-kit 命名空间下的其他子包。

Core library 核心库

为了保持核心库的规模,@dnd-kit/core 只提供了大多数用户在构建拖放界面时需要的主要构建模块。

  • Context provider
  • Hooks for:
    • Draggable
    • Droppable
  • Drag Overlay
  • Sensors for:
    • Pointer
    • Mouse
    • Touch
    • Keyboard
  • Accessibility features

Modifiers 修改器

修改器让你动态地修改由传感器检测到的运动坐标。它们可以用于广泛的使用情况,例如。

  • 将运动限制在单个轴上
  • 将运动限制在可拖动节点容器的边界矩形内
  • 将运动限制在可拖动节点的滚动容器边界矩形内
  • 施加阻力或夹住运动(原文为 clamping the motion)

修改器库包含了许多有用的修改器,可以应用于 DndContext 以及 DraggableClone

要开始使用修改器,请通过 「yarn」 或 「npm」 安装修改器包。

npm install @dnd-kit/modifiers

Presets 预设

Sortable 可排序

如果你选择从头开始建立一个可排序的界面, @dnd-kit/core 提供了所有你需要的构件,但幸运的是你不需要这样做。

如果你打算建立一个可排序界面,我们强烈建议你试试 @dnd-kit/sortable,它是建立在 @dnd-kit/core 之上的一个小层,为建立丝般顺滑、灵活和可访问的可排序界面而优化。

Development releases 开发版本

每一次合并到 @dnd-kit 主分支的提交都会触发一个开发构建,并在下一个标签下发布到 「npm」。

要使用开发版本,请用@next 标签安装你打算使用的 @dnd-kit

npm install @dnd-kit/core@next @dnd-kit/sortable@next

Quick start 快速开始

急于开始?本快速入门指南将帮助你熟悉 「dnd kit」 的核心概念。

在开始使用之前,请确保你已经遵循了安装指南中的安装步骤。

Context provider 设置上下文

首先,我们将设置应用程序的总体结构。为了使 useDraggableuseDroppable hooks 能够正常工作,你需要确保使用它们的组件被包裹在 <DndContext /> 组件中。

// App.jsx
import React from 'react'
import { DndContext } from '@dnd-kit/core'

import { Draggable } from './Draggable'
import { Droppable } from './Droppable'

function App() {
  return (
    <DndContext>
      <Draggable />
      <Droppable />
    </DndContext>
  )
}

Droppable

接下来,让我们来设置你的第一个 Droppable 组件。 要做到这一点,我们将使用 useDroppable hooks。

useDroppable hooks 对你的应用程序应该如何结构并无要求。但至少,它要求你传递一个你想 droppable 的 DOM 引用。你还需要为你的所有的 droppable 组件提供一个唯一 id 属性。

当一个可拖动的元素被移到你的 droppable 元素上时,isOver 属性将变为真。

// Droppable.jsx
import React from 'react'
import { useDroppable } from '@dnd-kit/core'

function Droppable(props) {
  const { isOver, setNodeRef } = useDroppable({
    id: 'droppable',
  })
  const style = {
    color: isOver ? 'green' : undefined,
  }

  return (
    <div ref={setNodeRef} style={style}>
      {props.children}
    </div>
  )
}

Draggable

接下来,让我们看看如何实现我们的第一个 Draggable 组件。要做到这一点,我们将使用 useDraggable hooks。

useDraggable hooks 对你的应用程序的结构没有特殊要求。它要求您能够将监听器和一个 ref 添加到您希望成为 draggable 的 DOM 元素。您还需要为您的所有 draggable 组件提供一个唯一的 id 属性。

在一个 draggable 的项目被拾起后,transform 属性将被填充为您在屏幕上移动该项目所需的平移坐标。

transform 对象遵循以下格式:

{
  x: number,
  y: number,
  scaleX: number,
  scaleY: number
}
// Draggable.jsx
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>
  )
}

正如你从上面的例子中看到的,实际上只需要几行就可以将你现有的组件转化为可拖动的组件。

出于性能方面的考虑,我们建议你使用 transform 而不是其他位置性 CSS 属性来移动被拖动的元素。

你可能想改变 Draggable 组件的 z-index,以确保它出现在其他元素的顶部。

如果你的项目需要从一个容器移动到另一个容器,我们建议你使用 <DragOverlay> 组件。

transform 对象转换为字符串可能会感觉很繁琐。别担心,你可以通过从 @dnd-kit/utilities 包中导入 CSS 工具来避免手工操作。

import { CSS } from '@dnd-kit/utilities'

// Within your component that receives `transform` from `useDraggable`:
const style = {
  transform: CSS.Translate.toString(transform),
}

组装所有的部件 Assembling all the pieces

当你设置了 DroppableDraggable 组件后,回到你设置<DndContext>组件的地方,在这里你可以添加事件监听器,以便能够响应不同的事件。

在这个例子中,我们假设你想把 <Draggable> 组件从外面移到 <Droppable> 组件中。

为此,您要监听 <DndContext>onDragEnd 事件,以查看您的 draggable 项目是否被放在您的 droppable 中。

// App.jsx
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 ? draggableMarkup : null}
      <Droppable>{isDropped ? draggableMarkup : 'Drop here'}</Droppable>
    </DndContext>
  )

  function handleDragEnd(event) {
    if (event.over && event.over.id === 'droppable') {
      setIsDropped(true)
    }
  }
}

这就是了!你已经设置了你的第一个 DroppableDraggable 组件

接下来

把事情推得更远一点

我们在上面设置的例子有点简单化。在现实世界的例子中,你可能有多个可拖动的容器,而且你可能还想在你的物品被拖动到可拖动的容器中后能够将其拖回。

下面是一个稍微复杂的例子,它包含了多个 Droppable 容器。

// App.jsx
import React, {useState} from 'react';
import {DndContext} from '@dnd-kit/core';

import {Droppable} from './Droppable';
import {Draggable} from './Draggable';

function App() {
  const containers = ['A', 'B', 'C'];
  const [parent, setParent] = useState(null);
  const draggableMarkup = (
    <Draggable id="draggable">Drag me</Draggable>
  );

  return (
    <DndContext onDragEnd={handleDragEnd}>
      {parent === null ? draggableMarkup : null}

      {containers.map((id) => (
        // We updated the Droppable component so it would accept an `id`
        // prop and pass it to `useDroppable`
        <Droppable key={id} id={id}>
          {parent === id ? draggableMarkup : 'Drop here'}
        </Droppable>
      ))}
    </DndContext>
  );

  function handleDragEnd(event) {
    const {over} = event;

    // If the item is dropped over a container, set it as the parent
    // otherwise reset the parent to `null`
    setParent(over ? over.id : null);
  }
};
// Droppable.jsx
import React from 'react';
import {useDroppable} from '@dnd-kit/core';

export function Droppable(props) {
  const {isOver, setNodeRef} = useDroppable({
    id: props.id,
  });
  const style = {
    color: isOver ? 'green' : undefined,
  };
  
  
  return (
    <div ref={setNodeRef} style={style}>
      {props.children}
    </div>
  );
}
// Draggable.jsx
import React from 'react';
import {useDraggable} from '@dnd-kit/core';

export function Draggable(props) {
  const {attributes, listeners, setNodeRef, transform} = useDraggable({
    id: props.id,
  });
  const style = transform ? {
    transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
  } : undefined;

  
  return (
    <button ref={setNodeRef} style={style} {...listeners} {...attributes}>
      {props.children}
    </button>
  );
}

我们希望这份快速入门指南能让你领略到@dnd-kit的简单和强大。还有很多东西需要学习,我们鼓励你继续阅读所有可以传递给<DndContext>useDroppableuseDraggable的选项,阅读它们各自的 API 文档。