react-beautiful-dndを使ったドラッグ&ドロップ並べ替え機能【ReactとTypeScriptで実現】

本記事では、react-beautiful-dndを使用して、ドラッグ&ドロップの並べ替え機能を実装する方法について解説する。react-beautiful-dndは、Reactで簡単にドラッグ&ドロップの機能を追加するためのもので、並べ替えやリストの管理に適している。react-beautiful-dndのインストールから、基本的な設定、並べ替え機能の実装、TypeScriptによる型定義までを詳しく説明する。

記事のゴール

以下のようなリストをドラッグ&ドロップで並べ替えれるようにする。

検証環境

今回の実装は以下の環境で検証を行っている。

  • react: “^17.0.0 || ^18.0.0”
  • next: “14.2.12”
  • typescript: “^5”

インストール

まず、react-beautiful-dndをプロジェクトにインストールする。
npm installコマンドで、react-beautiful-dndと、TypeScript用の型定義を追加する。

$ npm install react-beautiful-dnd
$ npm install -D @types/react-beautiful-dnd

インストール後のpackage.jsonは以下のようになる。

"dependencies": {
  "@types/react-beautiful-dnd": "^13.1.8",
  "react-beautiful-dnd": "^13.1.1",
}

【結論】コードの全体像

index.tsx

以下が、ドラッグ&ドロップ並べ替え機能を実現するための全体コード。コピペで動作する。

import React, { useState } from "react";
import { DragDropContext, Droppable, Draggable, DropResult } from "react-beautiful-dnd";

const initialItems = [
  { id: "1", content: "Item 1" },
  { id: "2", content: "Item 2" },
  { id: "3", content: "Item 3" },
];

export default function Index() {
  const [items, setItems] = useState(initialItems);

  const onDragEnd = (
    result: DropResult,
    items: { id: string; content: string }[],
    setItems: React.Dispatch<React.SetStateAction<{ id: string; content: string }[]>>
  ) => {
    const { source, destination } = result;

    // ドロップ先が存在しない場合、終了
    if (!destination) return;

    const reorderedItems = Array.from(items);
    const [removed] = reorderedItems.splice(source.index, 1);
    reorderedItems.splice(destination.index, 0, removed);

    setItems(reorderedItems);
  };

  return (
    <DragDropContext onDragEnd={(result) => onDragEnd(result, items, setItems)}>
      <Droppable droppableId="droppable">
        {(provided) => (
          <div
            {...provided.droppableProps}
            ref={provided.innerRef}
            style={{ padding: 20, width: 250 }}
          >
            {items.map((item, index) => (
              <Draggable key={item.id} draggableId={item.id} index={index}>
                {(provided) => (
                  <div
                    ref={provided.innerRef}
                    {...provided.draggableProps}
                    {...provided.dragHandleProps}
                    style={{
                      padding: 16,
                      marginBottom: 8,
                      backgroundColor: "#f2f2f2",
                      border: "1px solid #ddd",
                      borderRadius: 4,
                      ...provided.draggableProps.style,
                    }}
                  >
                    {item.content}
                  </div>
                )}
              </Draggable>
            ))}
            {provided.placeholder}
          </div>
        )}
      </Droppable>
    </DragDropContext>
  );
}

コード解説

上記のコードは、react-beautiful-dndを用いてシンプルなドラッグ&ドロップの並べ替え機能を実装している。以下、コードの重要なポイントを詳しく解説する。

1. 必要なモジュールと型のインポート

import React, { useState } from "react";
import { DragDropContext, Droppable, Draggable, DropResult } from "react-beautiful-dnd";
  • DragDropContext: ドラッグ&ドロップ機能のコンテキストを提供するコンポーネント。
  • Droppable: ドロップ可能な領域を定義するコンポーネント。
  • Draggable: ドラッグ可能な要素を定義するコンポーネント。
  • DropResult: ドラッグ&ドロップ操作の結果を表す型定義。

DragDropContext

DragDropContextはドラッグ&ドロップ全体のコンテナであり、
onDragEndプロパティでドラッグ完了時のイベントハンドラーを指定する。
これにより、ドラッグ完了後にどのようにリストを更新するかを制御できる。

<DragDropContext onDragEnd={(result) => onDragEnd(result, items, setItems)}>
  ...
</DragDropContext>

onDragEnd:ドラッグ操作が終了したときに呼び出され、アイテムの並べ替え処理を行う関数。
resultパラメータにはドラッグの詳細情報が含まれている。

Droppable

Droppableコンポーネントは、アイテムをドロップできるエリアを定義する。
このコンポーネントの子要素には、リスト全体のレイアウトが含まれる。

<Droppable droppableId="droppable">
  {(provided) => (
    <div
      {...provided.droppableProps}
      ref={provided.innerRef}
      style={{ padding: 20, width: 250 }}
    >
      ...
      {provided.placeholder}
    </div>
  )}
</Droppable>
  • droppableIdDroppableを識別するための一意なID。
    今回の例では単一のリストのため「droppable」としている。
  • provided.droppablePropsprovided.innerRefreact-beautiful-dndによって追加される属性で、Droppableエリアの参照と必要な属性を設定する。
  • provided.placeholder:ドラッグ中にリストの見た目が崩れないように空白スペースを確保するための要素。

Draggable

Draggableコンポーネントは、ドラッグ可能なアイテムを定義する。
各アイテムが一意に識別されるようにdraggableIdindexプロパティが必要。

<Draggable key={item.id} draggableId={item.id} index={index}>
  {(provided) => (
    <div
      ref={provided.innerRef}
      {...provided.draggableProps}
      {...provided.dragHandleProps}
      style={{
        padding: 16,
        marginBottom: 8,
        backgroundColor: "#f2f2f2",
        border: "1px solid #ddd",
        borderRadius: 4,
        ...provided.draggableProps.style,
      }}
    >
      {item.content}
    </div>
  )}
</Draggable>
  • draggableId:アイテムを一意に識別するためのID。
  • provided.draggableProps:アイテムがドラッグ可能であることを示すための属性。
  • provided.dragHandleProps:アイテムをドラッグするためのハンドル属性を提供する。これを使用することで、アイテム全体がドラッグ対象になる。

onDragEnd関数

onDragEnd関数は、ドラッグ終了時に呼び出され、DropResultオブジェクトを引数に受け取る。このオブジェクトには、source(ドラッグ元の情報)とdestination(ドロップ先の情報)が含まれており、これをもとにアイテムの並べ替えを行う。

const onDragEnd = (
  result: DropResult,
  items: { id: string; content: string }[],
  setItems: React.Dispatch<React.SetStateAction<{ id: string; content: string }[]>>
) => {
  const { source, destination } = result;

  // ドロップ先が存在しない場合は処理を終了
  if (!destination) return;

  const reorderedItems = Array.from(items);
  const [removed] = reorderedItems.splice(source.index, 1);
  reorderedItems.splice(destination.index, 0, removed);

  setItems(reorderedItems);
};
  • source:ドラッグ元のインデックス位置を持つ情報。
  • destination:ドロップ先のインデックス位置を持つ情報。
  • reorderedItems:現在のリストを複製した配列。リストを直接変更するのではなく複製した配列を操作することで、不変性が保たれる。
  • spliceメソッド:source.index位置から1つのアイテムを削除し、destination.indexに挿入する。この操作によって、アイテムの並び順が変更される。

まとめ

この記事では、react-beautiful-dndを用いて、リストのドラッグ&ドロップによる並べ替え機能を実装した。DragDropContextDroppableDraggableという3つの基本コンポーネントの役割を理解し、onDragEnd関数を用いてアイテムの並べ替えを適切に制御できる。

react-beautiful-dndのGitHubリポジトリ: https://github.com/atlassian/react-beautiful-dnd

関連記事

コメント

この記事へのトラックバックはありません。