dnd Kitをプロダクトで利用して感じたメリットとハマりポイント
こんにちは!
株式会社Sally 新人エンジニアの @haruten です♪
私たち株式会社Sallyでは、マーダーミステリーをスマホやPCで遊べるアプリ「ウズ」や、マーダーミステリーを制作してウズ上で公開・プレイできるエディターツール「ウズスタジオ」などを開発・運営しています。
今回はウズスタジオで実際に使われているReact向けドラッグ&ドロップライブラリの「dnd kit」を初めて触ってみて感じた便利な点、ハマってしまった点を紹介していきます!
dnd kitとは?
- React向けに作られたドラッグ&ドロップ(Drag and Drop)UIのライブラリ
- 軽量かつ柔軟な設計が特徴で、複雑な状態管理を最小限に抑えながら、直感的なドラッグ操作を実装が可能
dnd kitは以下のような点で優れています。
-
縦横の二次元方向の並び替えに対応
-
ドラッグ対象・ドロップ先の定義がシンプル
-
状態管理が柔軟にできる
細かい制御が効くため、単なるリストの並び替えだけでなく、ダッシュボードやボードUIのような複雑なレイアウトでも活用できます。
簡単な実装例(例:並べ替え可能なリスト)
dnd kitは少ないコードで、シンプルな並び替えUIを実装することができます。
以下は、縦方向に並んだリストをドラッグで並べ替える最小構成の例です。
import { DndContext, closestCenter } from '@dnd-kit/core';
import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import React, { useState } from 'react';
const Item = ({ id }: { id: string }) => {
// useSortableが提供するプロパティ・イベントハンドラを取得
const {
attributes, // キーボード操作などに必要な属性
listeners, // ドラッグイベントをバインドするハンドラ
setNodeRef, // DOM要素のrefを設定するための関数
transform, // ドラッグ中の位置(x, y)
transition, // ドラッグ時のアニメーション設定
} = useSortable({ id });
// ドラッグ中の見た目を制御するスタイル
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
return (
<div
ref={setNodeRef}
style={style}
{...attributes} // キーボード操作のための属性
{...listeners} // マウス・タッチ操作のイベントハンドラ
>
{id}
</div>
);
};
export default function SortableList() {
// 並び替え対象のアイテムリストをuseStateで管理
const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
return (
<DndContext
onDragEnd={({ active, over }) => {
// ドラッグが完了したときの処理
if (active.id !== over?.id) {
const oldIndex = items.indexOf(active.id as string);
const newIndex = items.indexOf(over?.id as string);
setItems(arrayMove(items, oldIndex, newIndex));
}
}}
>
{/* 複数のSortable要素をまとめて扱うためのContext */}
<SortableContext
items={items}
strategy={verticalListSortingStrategy}// 縦方向の並び替えを指定
>
{items.map((id) => (
<Item key={id} id={id} />
))}
</SortableContext>
</DndContext>
);
}
このようにdnd kitでは、ドラッグ対象とその順序管理をシンプルに構築できます。
useSortable + SortableContext + DndContext の3つを理解すれば、基本的な並び替えはすぐに実装可能です!!
使って便利だった点
dnd kitを使ってみて、特に「これは良い」と感じたのは以下のポイントです。
二次元方向の並び替えに対応している
dnd kitの一番の魅力は、縦だけでなく横方向やグリッドレイアウトにも対応している点です。
例えば、「アイテムをエリア内で自由に並べ替える」UIでは、単純な縦並びだけでは足りません。dnd kitは rectSortingStrategy やカスタムの戦略を使うことで、複数行・複数列の要素を自然にドラッグ&ドロップで移動させられます。
これにより、エディタUIやボード型のUIを構築する際にも無理なく使うことができました。
シンプルなAPIで直感的に使える
dnd kitは「Sortable」を中心とした構成がわかりやすく、
以下のような構造を理解すればすぐに使い始められます:
DndContext:全体のドラッグ操作を管理
SortableContext:並び替え対象の要素リストを囲む
useSortable:各アイテムをドラッグ可能にする
ドキュメントも簡潔で、チュートリアルに沿って進めれば基本的な実装は数十分で完了します。
状態管理が柔軟にできる
並び替えの順序を onDragEnd で自由に制御できるため、Reactの状態管理ときれいに分離して書けるのも嬉しいポイントです。
グローバルステートを使わず、useState や useReducer のみで完結するケースが多く、学習コストも低めです。
注意点・ハマりポイント
dnd kitは柔軟で高機能ですが、いくつか注意すべき点やハマりやすいポイントがありました。
Next.js の Link コンポーネントとは相性が悪い
dnd kitの useSortable で返される listeners を Next.js の <Link> コンポーネントにそのまま渡すと、ドラッグ開始時にページ遷移が発生してしまうことがあります。
これは Link に内部的に使われている <a> タグが click イベントを拾ってしまうためです。
この問題に対応するためには、以下のような対応が必要になります。
-
<Link> コンポーネントで要素全体をラップせず、クリック領域とドラッグ領域をUI上で分離する
-
Linkを使わず、onClick でページ遷移を制御する
可能であれば、クリックとドラッグの領域をUIレベルで分けるのが理想的です。
また、onClick による遷移では以下のようなデメリットもあります.
-
アクセシビリティが低下する(キーボード操作,別タブでの遷移が難しくなる等)
-
<Link> によるプリフェッチ機能が使えなくなるため、パフォーマンス面でも不利になる
こうした点も考慮しつつ、UIの設計段階でドラッグとクリックをきれいに分離する工夫が必要です。
並び替え対象の要素同士のサイズが異なる場合、表示崩れが起こる
dnd kitでは、ドラッグ&ドロップの精度とパフォーマンスを両立するために、要素の位置やサイズをキャッシュしています。
そのため、並び替え対象の要素同士のサイズが異なると、表示や移動時にズレやバグが発生することがあります。
具体的には、レイアウトが崩れるといった現象が起こります。
こういった問題への対策としては:
- ドラッグ中のサイズを固定する
(例)縦方向リストの場合
const style = {
transform: CSS.Transform.toString(transform),
transition,
height: '100%', // ドラッグ中も高さを維持
};
- layoutShiftCompensation を無効化して、自動補正によるズレを防ぐ
などの方法が有効です。
dnd Kitは自由度が高く、細かいUI要件にも柔軟に対応できる一方で、内部の仕組みをある程度理解しておかないと、意図しない表示崩れに悩まされることもあります。
まとめ
dnd kitは、Reactでドラッグ&ドロップを実装する上でとても強力なライブラリです。
特に以下のようなケースでは、導入を検討する価値があると感じました。
-
二次元的な並び替えや柔軟なUIレイアウトを実現したい
-
並び替え処理を、最小限のコードで導入したい
一方で、Next.jsのLinkとの相性やスタイル周りの自由度の高さには注意が必要です。
とはいえ、設計思想が明快で学習コストも比較的低く、実践投入しやすいライブラリだと感じました。
マーダーミステリー制作ツール「ウズスタジオ」でも大活躍してくれているdnd kit。
複雑なUIが求められるフロントエンド開発において、強力な味方になるはずです。
Discussion