4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Next.js]はじめてのdnd-kit その1(基本編)

Last updated at Posted at 2024-08-27

はじめに

Next.jsを触ってみたいと、学習用に簡単なアプリを作っており、その中で新たに試したことを記事にしています。
同じようにこれからNext.js始めてみようという方の参考になれば嬉しいです。

やりたいこと

dndライブラリについて、どのようなものか知りたい。
今回は基本的な部分のみの記事です。

「はじめてのdnd-kit」シリーズ全4回の1回目です。
続きはこちら

環境

下記のDocker開発環境にて行います。

DnDライブラリについて

Next.jsで使えるDnDライブラリの選択肢がいくつかありました。

1. react-beautiful-dnd

2. react-dnd

  • 特徴:細かい制御が可能。その分、DnDのロジックを自分で制御する必要がある
  • 公式サイト:react-dnd

3. React Flow

  • 特徴:ビジュアルエディタやフロー構築ツールに適したライブラリ。エッジを接続したりするインタラクティブなグラフ作成に最適
  • 公式サイト:React Flow

4. dnd-kit

  • 特徴:軽量でモダンなDnDライブラリ。低レベルAPIと高レベルAPIの両方を提供しており、シンプルなDnDから複雑なDnDインターフェースまでカバーできる
  • 公式サイト:dnd-kit

今回の選択

  • react-beautiful-dnd: メンテナンスが終了している
  • react-dnd: ロジック制御が必要(初めてなのでよりシンプルなものを希望)
  • React Flow: フロー構築向け(用途違い)

→今回は軽量なのと、シンプルな実装も可能なdnd-kitを使うことに決めました。

dnd-kitについて

特徴

詳細については、公式サイトに記載されていますので割愛しますが、良いなと思った点は下記です。

  • 軽量:ライブラリのコア部分は約10KBとものすごく軽量でサクサク動く
  • 依存関係なし:外部の依存関係がない状態で動作するように設計されているため、他のパッケージの更新や互換性問題を気にする必要がない
  • Reactに最適化:useDraggableやuseDroppableなどのカスタムフックが予め準備されており、機能を簡単に組み込むことができる

基本コンポーネントとフック

DndContext

ドラッグ&ドロップの設定を行うためのコンテキスト。
全てのドラッグ&ドロップ操作はこのコンテキストの中で行われるため、これで全体を囲みドラッグ&ドロップ機能を有効にします。

useDroppable

ドロップ可能な領域を作成するためのフック。
これを使って、移動先(カードの受け入れ先)の領域を作成します。

useDraggable

ドラッグ可能な要素を作成するためのフック。
これを使って、今回でいうところの動かすチケット(カード)を作成します。

イメージ

DndContextで全体を囲みその中に、useDroppable,useDraggableで作成したコンポーネントが入るイメージ。

<DndContext> // 全体を囲む
    <Draggable />  // これをドラッグ出来る(動かせる)
    <Droppable />  // ここにドロップ出来る(ここに移動出来る)
</DndContext>

クイックスタートでお試し

まずは簡単にクイックスタートに沿ってサンプルを作って試してみます。

インストール

以下のコマンドを実行して、@dnd-kit/coreパッケージをインストールします。

npm install @dnd-kit/core

Droppable.tsx

ドロップするコンポーネントからuseDroppableフックを使って作成します。
最低限idを渡す必要があり、idは全てのDroppableコンポーネントで一意にする必要があります。

返り値について

  • setNodeRef:参照を設定する関数。操作したいコンポーネントのrefとして登録し、追跡して他のドラッグしてきた要素との衝突や交差を検出できるようにする
  • isOver:ドラッグ可能な要素がドロップ可能な要素の上に移動した時にTrueになる
    それを使って、重なった時に色を変えたり大きさを変えたりして、ユーザーにドロップ可能であることのヒントを与えることができる
Droppable.tsx
import {useDroppable} from '@dnd-kit/core';

type DroppableProps = {
  children: React.ReactNode;
  id: string;
  isOverAddClass?: string;
};

export default function Droppable({children, id, isOverAddClass}: DroppableProps) {
  const {isOver, setNodeRef} = useDroppable({
    id: id,
  });

  return (
    <div ref={setNodeRef} className={isOver && isOverAddClass ? isOverAddClass : ""}>
      {children}
    </div>
  );
}

Draggable.tsx

続いてドラッグするコンポーネントをuseDraggableを使って作成します。
こちらも最低限idを渡す必要があり、全てのDraggableコンポーネントで一意にする必要があります。

返り値について

  • setNodeRef:参照を設定する関数。こちらも操作したいコンポーネントのrefとして登録が必要
  • transform:Draggableアイテムが選択されると、画面上でアイテムを移動するために必要な座標が設定される
    これを使って、ドラッグ移動を描画できる
  • attributes:Draggableアイテムに適した一連のデフォルト属性を提供してくれる
  • listeners:ドラッグ操作のイベントリスナーを含むため、操作したいコンポーネントに登録する
import {useDraggable} from '@dnd-kit/core';
import {CSS} from '@dnd-kit/utilities';

type DraggableProps = {
  children: React.ReactNode;
  id: string;
};

export default function Draggable({children, id}: DraggableProps) {
  const {attributes, listeners, setNodeRef, transform} = useDraggable({
    id: id,
  });

  const style = {
    transform: CSS.Translate.toString(transform),
  }
  
  return (
    <button ref={setNodeRef} style={style} {...listeners} {...attributes}>
      {children}
    </button>
  );
}

組み立てる

DndContextの中に、作成したDroppable、Draggableコンポーネントを組み込みます。
クイックスタートのものをそのまま使いました。
クライアントコンポーネントにしないと動かせないので"use client"を付けてます。

"use client";

import { DndContext, DragEndEvent, UniqueIdentifier } from '@dnd-kit/core';
import Droppable from '@/components/dnd_kit/Droppable';
import Draggable from '@/components/dnd_kit/Draggable';
import { useState } from 'react';


export default function Page() {
  const containers = ['A', 'B', 'C'];
  const [parent, setParent] = useState<UniqueIdentifier | null>(null);

  const draggableMarkup = (
    <Draggable id="draggable">
      <div className='cursor-grab w-48 h-20 bg-blue-200 flex justify-center items-center'>
        Drag me
      </div>
    </Draggable>
  );
  
  return (
    <DndContext onDragEnd={handleDragEnd}>
      <div className='flex flex-col justify-center items-center w-screen h-screen gap-8'>
        // ドラッグするアイテム
        <div className='flex h-20'>
          {parent === null ? draggableMarkup : null}
        </div>
        // ドロップするコンテナ3個横並び
        <div className='flex'>
          {containers.map((id) => (
            <Droppable key={id} id={id} isOverAddClass="bg-green-700">
              <div className='w-52 h-24 border-2 border-dashed border-gray-100/50 flex justify-center items-center'>
                {parent === id ? draggableMarkup : 'Drop here'}
              </div>
            </Droppable>
          ))}
        </div>
      </div>
    </DndContext>
  );
  
  function handleDragEnd(event: DragEndEvent) {
    const {over} = event;
    setParent(over ? over.id : null);
  }
}

前準備

1. ドロップエリアのコンテナとしてA、B、C を配列で準備
2. ドラッグしたコンポーネントがどこのコンテナに入ったかを管理するparentをuseStateで作成
3. 前項で作っておいたDraggable を使って、draggableMarkupを準備

組み立て

  1. 一番外側はDndContextで囲む
    onDragEndで、ドラッグしたアイテムがドロップされた後に実行する処理を設定できる。
    eventの返り値として受け取っているoverはDraggableがDraggable上でドロップされなければnullとなる。Draggable上でドロップされれば、ドロップ先のコンテナのidを含むので、parentへセット
  2. ドラッグするアイテムを配置する領域は、parentがnull(Draggable上にドロップされていない状態)の時はドラッグするアイテムとしてdraggableMarkupを配置。すでにDraggable上にドロップいれば、何も表示しない
  3. ドロップエリアは3個コンテナ(エリア)を並べるため、map関数を利用し、idにはそれぞれのコンテナの値(A,B,C)を渡す。
    parent(ドロップ先のコンテナのid)が自身のコンテナidと一致すれば、draggableMarkupを配置。
    ※上記2か3のいずれかにdraggableMarkupが表示されることになる

テスト

このような画面になりました。

image.png

DraggableがDraggableに重なった状態(isOverがtrueの状態)では、設定していた背景色が変わりました。

image.png

3つのコンテナそれぞれ、ドロップ出来ました。

image.png

image.png

併せて、枠外にドラッグ&ドロップした場合は、無事初期状態に戻すことが出来ました。

さいごに

・とりあえず、基本中の基本の部分だけは、使い方が分かりました。
・次回はdnd-kit/sortableを使って並び替えを実装してみます。

つづき

参考

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?