はじめに
React で自前の Drag&Drop (DnD) を実装するのは、なかなか大変だと思います。
ライブラリを探していたところ、Atlassian 製の react-beautiful-dnd というライブラリがよさそうだったので使ってみての所感を書きます。
公式サンプル
縦方向リストの DnD
横方向リストの DnD
関数コンポーネントで実装
2カラム間の DnD
ヌルヌル動いて気持ち〜〜〜
手元で実装してみる
バージョン
"typescript": "^3.7.3",
"react": "^16.12.0",
"react-beautiful-dnd": "^12.2.0",
"styled-components": "^4.4.1"
作ったのはこれ
(見た目はほぼサンプルと同じだねとか、、聞こえてますよ、、ええ)
↓ディレクトリ構成など分かりやすいようにアレンジしてあるので参考にしてみてください↓
https://github.com/kk-web/react-beautiful-dnd_sample
インストール
※ React まわりの構築は割愛します。
まずは、モジュールをインストール!
npm install --save react-beautiful-dnd
元になるリストを作る
import React from "react";
import { ItemType } from "./types";
import List from "./List";
const App = () => {
const initial: ItemType[] = Array.from({ length: 10 }, (v, k) => k).map(k => {
return {
id: `id-${k}`,
content: `Item ${k}`
};
});
return <List items={initial} />;
};
export default App;
export type ItemType = {
id: string;
content: string;
};
import React from "react";
import Item from "./Item";
const List = ({ items }) => (
<>
{items.map(item => (
<Item item={item} key={item.id} />
))}
</>
);
export default List;
import React from "react";
import styled from "styled-components";
const StyledItem = styled.div`
width: 200px;
border: 1px solid grey;
margin-bottom: 8px;
background-color: lightblue;
padding: 8px;
`;
const Item = ({ item }) => {
return <StyledItem>{item.content}</StyledItem>;
};
export default Item;
型定義
Typescript
If you are using TypeScript you can use the community maintained DefinitelyTyped type definitions. Installation instructions.
とあります。型定義モジュールもインストールしておきましょう。
npm install --save @types/react-beautiful-dnd
Here is an example written in typescript.
サンプルも用意されています!
DnD 実装
import React, { useState } from "react";
import { DragDropContext, Droppable } from "react-beautiful-dnd";
import { ItemType } from "./types";
import List from "./List";
const reorder = (
list: ItemType[],
startIndex: number,
endIndex: number
): ItemType[] => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
const App = () => {
const initial: ItemType[] = Array.from({ length: 10 }, (v, k) => k).map(k => {
return {
id: `id-${k}`,
content: `Item ${k}`
};
});
const [state, setState] = useState({ items: initial });
const onDragEnd = result => {
if (!result.destination) {
return;
}
if (result.destination.index === result.source.index) {
return;
}
const items = reorder(
state.items,
result.source.index,
result.destination.index
);
setState({ items });
};
return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="list">
{provided => (
<div ref={provided.innerRef} {...provided.droppableProps}>
<List items={state.items} />
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
};
export default App;
↓変更ありません
export type ItemType = {
id: string;
content: string;
};
import React from "react";
import Item from "./Item";
const List = React.memo<{ items }>(({ items }) => (
<>
{items.map((item, index: number) => (
<Item item={item} index={index} key={item.id} />
))}
</>
));
export default List;
import React from "react";
import styled from "styled-components";
import { Draggable } from "react-beautiful-dnd";
const StyledItem = styled.div`
width: 200px;
border: 1px solid grey;
margin-bottom: 8px;
background-color: lightblue;
padding: 8px;
`;
const Item = ({ item, index }) => {
return (
<Draggable draggableId={item.id} index={index}>
{provided => (
<StyledItem
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{item.content}
</StyledItem>
)}
</Draggable>
);
};
export default Item;
Server Side Rendering
Next.js で実装したときにつまづきました、、
Drag できなくて、console を見てみると
react-beautiful-dnd A setup problem was encountered.
> Invariant failed: Draggable[id: id-9]: Unable to find drag handle👷 This is a development only message. It will be removed in production builds.
とエラーが出ていました。
ドキュメントを見てみると
API 🏋
(中略)
resetServerContext() - Utility for server side rendering (SSR)
と記載が!
SSR の場合は、任意の場所でこの関数を実行しましょう!
おわりに
DnD を自作で実装しようとすると手間がかかりますが、react-beautiful-dnd を使えば数行の実装用関数と state や props を設定するだけで、ヌルヌル動くものができるのはありがたいですね!
公式サンプルではコンポーネントが1つのファイルにまとめられていますが、今回の実装のようにリストやアイテムで分解すれば、既存のリストにも適用しやすいのではないかと思います。