🦁

React で DnD するなら、dnd kit

2022/10/08に公開
2

久しぶりの記事になってしまいました。

最近、React でドラッグ&ドロップ(以下、DnD)を実装する機会があり、いい感じのライブラリないか探していました。
React の DnD ライブラリというと、以下の2つが代表的かと思います。

react-dnd は個人的になんかとっつきにくく、react-beautiful-dndは Strict Mode をオフにしないと動かない問題があり他のライブラリを探していたところ、今回 dnd-kit という良さげなものに出会いました。サイトのデザインもスタイリッシュで期待が高まります。
https://dndkit.com/

npm trends を見ると、他の2つにはまだ大きく差をつけられていますが、じわじわと伸びているようです。それにしても、react-dnd と react-beautiful-dnd はすごい拮抗しているんですね。

https://npmtrends.com/@dnd-kit/core-vs-react-beautiful-dnd-vs-react-dnd

今回一通り触ってみましたので、どんな感じがご紹介したいと思います。

概要

以下、公式サイトに書かれている内容の要約になります。

Overview(抜粋)

dnd-kit の特徴としては以下のようなものが挙げられます。

  • 機能が豊富
    • カスタマイズ可能な衝突検知アルゴリズム、複数のアクティベーター、ドラッグ可能オーバーレイ、ドラッグハンドラー等の多くの機能を提供しています
  • React製
    • 提供されている2つのhooks、 useDraggableuseDroppable を使えば、DOM のラッパーを作成することなくすぐに DnD が実装できます
  • 外部依存もなくて軽量
    • コアは 10KB ほどに最小化されていて、他の外部ライブラリには依存していません
  • プリセットがある
    • ソートが必要な場合は、@dnd-kit/sortable を使ってみてと書いてあります

早速使ってみる

Context

他のライブラリも同じような形ですが、Provider をドラッグやドロップをさせたい要素全体を覆うように設定します。

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

ドロップ可能なコンポーネントを作成していきますが、そのためには useDroppable という hooks を利用します。
useDroppable に対しては以下の2つが必要です。

  • ドロップ可能にしたい領域のDOM要素に対して、ref を渡す
  • 全てのドロップ可能なコンポーネントで一意になるように、ユニークIDを振る

ドラッグ可能な要素が、ドロップ可能な要素の上に来ると、isOvertrue になります。

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

続いて、ドラッグ可能なコンポーネントを作ります。 useDraggable という hooks を利用します。
こちらは以下の設定が必要です。

  • ドラッグできるように、DOMイベントを検知する listenersattributes を要素に対して渡す
  • 全てのドロップ可能なコンポーネントで一意になるように、ユニークIDを振る

アイテムがドラッグされているとき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>
  );
}

ドロップとドラッグを組み合わせる

DroppableDraggable の要素が作成できたら、DnDContextonDragEnd を渡して完了です。

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>
  );

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

    // ドロップできる領域にドロップした時、親としてidをセット
    // それ以外はnullにする
    setParent(over ? over.id : null);
  }

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

      {containers.map((id) => (
        // idを受け取ったらDroppableコンポーネントを更新
        // useDroppable にpropsを渡す
        <Droppable key={id} id={id}>
          {parent === id ? draggableMarkup : 'Drop here'}
        </Droppable>
      ))}
    </DndContext>
  );
};

動くコードはこちら

Integration

tanstack/react-table と合わせて使ってみました!

  • @tanstack/react-table
  • @dnd-kit/core
  • @dnd-kit/modifiers
  • @dnd-kit/sortable
  • react-icons
GitHubで編集を提案

Discussion

miyamonzmiyamonz

ちょっとした報告ですが、react-dnd, react-beautiful-dnd のリンク先が逆になってますね

HamHam

あ、ほんとですね!ありがとうございます 🙇🏻