react-beautiful-dnd、react-dnd、react-sortablejs はすべて React 向けのドラッグ&ドロップ(DnD)機能を提供するライブラリですが、設計思想や対応範囲、実装方法に大きな違いがあります。react-beautiful-dnd は主にリストやグリッド内のアイテム再配置に特化し、物理的なアニメーションとアクセシビリティを重視した設計です。react-dnd はより汎用的な DnD フレームワークで、任意のコンポーネント間でのデータ転送や複雑なドロップロジックをサポートします。一方、react-sortablejs は既存の JavaScript ライブラリである SortableJS を React でラップしたもので、シンプルなソート操作に焦点を当てています。
Reactでドラッグ&ドロップ(DnD)機能を実装する際、react-beautiful-dnd、react-dnd、react-sortablejs はよく検討される3つの選択肢です。それぞれ設計目標が異なり、適切な選択はアプリケーションの要件に大きく依存します。この記事では、実際のコードを交えながら、技術的観点から深く比較します。
react-beautiful-dndこのライブラリは「リスト内でのアイテム移動」に特化しており、1つの <DragDropContext> 内で複数の <Droppable>(ドロップ可能な領域)と <Draggable>(ドラッグ可能なアイテム)を管理します。内部で独自の物理エンジンを用いてアニメーションを制御し、アクセシビリティ(キーボード操作、ARIA属性)を標準でサポートしています。
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
function App() {
const [items, setItems] = useState(['A', 'B', 'C']);
const onDragEnd = (result) => {
if (!result.destination) return;
const newItems = Array.from(items);
const [reorderedItem] = newItems.splice(result.source.index, 1);
newItems.splice(result.destination.index, 0, reorderedItem);
setItems(newItems);
};
return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="list">
{(provided) => (
<ul {...provided.droppableProps} ref={provided.innerRef}>
{items.map((item, index) => (
<Draggable key={item} draggableId={item} index={index}>
{(provided) => (
<li
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{item}
</li>
)}
</Draggable>
))}
{provided.placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
);
}
react-dndreact-dnd は「DnDのためのフレームワーク」であり、任意のコンポーネントをドラッグ可能またはドロップ可能にすることができます。useDrag と useDrop というカスタムフックを使って、各コンポーネントの振る舞いを宣言的に定義します。データ転送は「アイテム型(type)」と「ペイロード(item)」で抽象化され、複雑なロジックも表現可能です。
import { useDrag, useDrop } from 'react-dnd';
const ItemTypes = { CARD: 'card' };
function DraggableItem({ id, text, moveItem }) {
const [{ isDragging }, drag] = useDrag(() => ({
type: ItemTypes.CARD,
item: { id, text },
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
}));
return (
<div ref={drag} style={{ opacity: isDragging ? 0.5 : 1 }}>
{text}
</div>
);
}
function DropZone({ accept, onDrop }) {
const [{ isOver }, drop] = useDrop(() => ({
accept: ItemTypes.CARD,
drop: (item) => onDrop(item),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
}));
return (
<div ref={drop} style={{ backgroundColor: isOver ? '#f0f0f0' : '#fff' }}>
Drop here
</div>
);
}
react-sortablejsreact-sortablejs は、人気のある vanilla JavaScript ライブラリである SortableJS を React コンポーネントとしてラップしたものです。React の状態と直接同期するわけではなく、DOM 操作ベースで動作します。そのため、簡単なユースケースでは非常に手軽ですが、複雑な状態管理との連携には注意が必要です。
import { ReactSortable } from 'react-sortablejs';
function App() {
const [items, setItems] = useState([
{ id: '1', name: 'A' },
{ id: '2', name: 'B' },
{ id: '3', name: 'C' }
]);
return (
<ReactSortable
list={items}
setList={setItems}
tag="ul"
>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ReactSortable>
);
}
react-beautiful-dnd状態更新は onDragEnd コールバック内で明示的に行います。リストの変更は開発者が配列操作で実装する必要がありますが、その分、Redux や Zustand などの外部状態管理ライブラリとの統合が容易です。ただし、React の strict mode 下で二重レンダリングが発生すると、一時的なUIのちらつきが起こることがあります(公式ドキュメントで言及されています)。
react-dnd状態管理は完全に開発者に委ねられます。useDrag と useDrop は単に DnD のインタラクションをフックとして提供するだけで、データの更新は drop イベントハンドラ内で自由に記述できます。この柔軟性により、複雑な状態遷移や非同期処理も自然に組み込めます。
react-sortablejsReactSortable コンポーネントは list と setList プロパティを通じて React の状態と同期します。内部で SortableJS の onChange イベントを監視し、リストの順序が変わると setList を呼び出します。ただし、リスト項目の内容が変更された場合(例:名前編集)、SortableJS がそれを検知しないため、手動で再マウントや強制更新が必要になることがあります。
react-beautiful-dndアクセシビリティはコア設計原則の一つです。キーボードによるフォーカス移動、スペースキーでのドラッグ開始、矢印キーでの位置調整などが標準でサポートされています。また、タッチデバイスでも問題なく動作します(内部で pointer events を使用)。
react-dndアクセシビリティは開発者の責任です。HTML5 バックエンドを使用する場合、ネイティブの DnD はスクリーンリーダーとの互換性が低く、タッチデバイスでも動作しません。しかし、touch-backend を別途導入することでモバイル対応は可能ですが、アクセシビリティ対応は自分で実装する必要があります。
react-sortablejsSortableJS 自体がタッチデバイスをサポートしていますが、キーボード操作や ARIA 属性の自動付与といったアクセシビリティ機能は含まれていません。必要に応じて、開発者が追加で実装する必要があります。
react-beautiful-dnd: Droppable の droppableId を使って複数のリストを識別し、onDragEnd で source.droppableId と destination.droppableId を比較して移動先を判定します。const onDragEnd = (result) => {
const { source, destination } = result;
if (!destination) return;
if (source.droppableId === destination.droppableId) {
// 同一リスト内での並べ替え
} else {
// 異なるリスト間での移動
}
};
react-dnd: useDrop の accept オプションで受け入れるアイテム型を指定し、異なるコンポーネント間でデータをやり取りできます。さらに、canDrop を使ってドロップ可否を動的に制御できます。
react-sortablejs: group オプションを同じ値に設定することで、複数のリスト間でのアイテム移動を有効にできます。
<ReactSortable group="shared" list={list1} setList={setList1} />
<ReactSortable group="shared" list={list2} setList={setList2} />
react-beautiful-dnd: onDragEnd 内で条件をチェックし、条件に合わない場合は何もしない(元の位置に戻す)ことで実現します。
react-dnd: useDrop の canDrop 関数で条件を定義できます。これにより、ドロップゾーンの見た目(例:色変更)も動的に変更可能です。
const [{ canDrop, isOver }, drop] = useDrop(() => ({
accept: ItemTypes.CARD,
canDrop: (item) => item.type !== 'locked',
drop: (item) => { /* ... */ },
collect: (monitor) => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop(),
}),
}));
react-sortablejs: onMove オプションで移動可否を制御できます。<ReactSortable
onMove={(evt) => {
// evt.dragged でドラッグ中の要素を参照
// false を返すと移動をキャンセル
return evt.dragged.dataset.type !== 'locked';
}}
/>
react-beautiful-dndreact-dndreact-sortablejs| 要件 | 推奨ライブラリ |
|---|---|
| シンプルなリストの並べ替え | react-sortablejs |
| 洗練されたUIとアクセシビリティ | react-beautiful-dnd(※新規プロジェクトでは注意) |
| 複雑なDnDロジック(条件付きドロップ、複数型のアイテムなど) | react-dnd |
| 長期保守が見込まれる大規模アプリ | react-dnd |
| 最小限のセットアップで素早く実装 | react-sortablejs |
react-sortablejs が最も手軽です。ただし、将来的に要件が複雑化する可能性があるなら、最初から react-dnd を検討してもよいでしょう。react-beautiful-dnd は既存プロジェクトでの維持には使えますが、新規プロジェクトでは代替手段を検討することを強く推奨します。Atlassian 社自身が新しい DnD ライブラリを開発中であることが公表されています。react-dnd が最も堅実な選択です。初期設定はやや面倒ですが、一度慣れればあらゆる DnD シナリオに対応できます。どのライブラリを選ぶにせよ、ユーザー体験(特にアクセシビリティとモバイル対応)を常に意識することが重要です。
react-dnd は、複雑な DnD シナリオ(例:ツールボックスからキャンバスへの要素配置、異なる型のアイテム間での条件付きドロップなど)を実装する必要がある場合に適しています。HTML5 DnD API を抽象化しつつ、柔軟なバックエンド(HTML5、Touch、Test など)をサポートしており、大規模・長期運用が見込まれるアプリケーションに適しています。ただし、基本的なリストの並べ替えだけであれば、設定がやや冗長になる可能性があります。
react-beautiful-dnd は、リストやボード内でのアイテムの並べ替えや移動に特化した UI を構築したい場合に最適です。特に、スムーズなアニメーション、キーボード操作対応、タッチデバイス対応など、ユーザー体験とアクセシビリティを重視するプロジェクトに向いています。ただし、複数の DnD コンテキスト間でのアイテム移動や、カスタムデータペイロードの転送といった高度なユースケースには向いていません。
react-sortablejs は、シンプルかつ軽量にリストの並べ替え機能を実装したい場合に有効です。SortableJS の豊富なオプション(グループ間移動、フィルタリング、イベントフックなど)をそのまま利用でき、学習コストが低く、素早くプロトタイプを作成できます。ただし、React のライフサイクルとの統合が完全ではなく、状態管理との同期に注意が必要です。また、アクセシビリティやアニメーションの品質は他の2つに劣ります。
Drag and Drop for React.
See the docs, tutorials and examples on the website:
http://react-dnd.github.io/react-dnd/
See the changelog on the Releases page:
https://github.com/react-dnd/react-dnd/releases
Big thanks to BrowserStack for letting the maintainers use their service to debug browser issues.