ReactDnDについて
Reactでdrag&dropコンポーネントを実装するのにおそらく一番有名(Redux作った人が作った)かつドキュメントが豊富なパッケージです。ドキュメントの情報量が結構多く自由度が高くて混乱しやすいので軽く使ってみたい人向けに核となるところだけ解説します。Danさん本当好き。
API
各コンポーネントをdrggable&droppable化するためのAPIがES7のdecoratorとして提供されています。
babel6使っている人はdecoratorがまだ公式では対応していないみたいなので注意してください(babel5なら大丈夫です)。公式じゃなければbabel6用のプラグイン作っている人がいたと思うので探してみてください。
DragSource
ドラッグされるコンポーネントについての設定を行える。
import React from "react"; import { DragSource } from "react-dnd"; @DragSouce(type, spec, collect) export default class DragComponent extends React.Component { }
|
type
: dorpされる側はここで設定したtype
を見てdropを受け入れるか否かを決める、SymbolかString。
spec
: drag開始時の処理、drag終了時(dropされた時)の処理とかを書いたObject。例が後半にあります。
collect
: DragComponent
内で使う関数を取り出す関数でobjectを返す必要があります。connectとmonitorが引数として渡されます。ざっくり言うとconnectはDOMについて、monitorはdrag&dropの状態についてのObjectです、結構色々取れるので公式ドキュメント見てみてください。
DropTarget
ドロップされるコンポーネントについての設定を行える。
import React from "react"; import { DropTarget } from "react-dnd"; @DropTarget(types, spec, collect) export default class DropComponent extends React.Component { }
|
types
: dropを受け入れるtype
を設定する、SymbolかStringかArray。
spec
: dropを受け入れた時の処理やhoverされている時の処理を書いたObject。例が後半にあります。
collect
: DragSourceのcollect
と同じ
DragDropContext
上記のコンポーネント達をこいつでラップすることで初めてdrag&dropができるようになる。Backendが必要。
import React from "react"; import HTML5Backend from "react-dnd-html5-backend"; import { DragDropContext } from "react-dnd"; @DragDropContext(HTML5Backend) export default class DnDComponent extends React.Component { render() { return ( ) } }
|
例ではHTML5Backend
を使っているのでタッチには対応していません。タッチ対応のBackendもあるのでタッチ対応させたい人は探してみてください。
実装例
drag&dropするとメッセージを表示するコンポーネントを作ってみます。
DnDItem Componentを作る
実際にdrag&dropされるコンポーネントを作ります。
DragSourceとDropTargetは同時に使うことでdragもdropもできるコンポーネントを作ることが可能です。
drag&dropされたときのactionは親からpropsとして受け取ります。
またtypeも同様に親からpropsとして受け取っています。
DragSourceとDropTarget間は基本的にmonitorを通して値のやり取りをします。
import React from "react"; import { DragSource, DropTarget } from "react-dnd"; const dragSpec = { beginDrag(props) { const { id } = props; return { id }; }, endDrag(props, monitor) { const source = monitor.getItem(); const target = monitor.getDropResult(); if (target) props.dropAction(source.id, target.id); } } const dropSpec = { drop(props, monitor, component) { const { id } = props; return { id }; } } @DropTarget(props => props.type, dropSpec, connect => ({ connectDropTarget: connect.dropTarget() })) @DragSource(props => props.type, dragSpec, connect => ({ connectDragSource: connect.dragSource() })) export default class DnDItem extends React.Component { static propTypes = { connectDragSource: React.PropTypes.func.isRequired, connectDropTarget: React.PropTypes.func.isRequired, dropAction: React.PropTypes.func.isRequired, id: React.PropTypes.string.isRequired, name: React.PropTypes.string.isRequired }; render() { const { connectDragSource, connectDropTarget, name } = this.props; return connectDragSource(connectDropTarget( <li> <h3>{name}</h3> </li> )); } }
|
DnDField Componentを作る
DnDItem Componentをラップする親玉を作ります、stateはこのコンポーネントで管理して更新用のactionをDnDItemに渡します。
今回はidをkeyとして渡しています、keyはReactDnD内部でのDOMの参照に使われるのでValueObjectである必要があります。あまりにも適当なものを渡すと壊れるので気をつけてください(React始めたばっかりの時Math.random()とか渡してて死にそうになった)。
import React from "react"; import HTML5Backend from "react-dnd-html5-backend"; import { DragDropContext } from "react-dnd"; import DnDItem from "./DnDItem"; @DragDropContext(HTML5Backend) export default class DnDField extends React.Component { constructor(props) { super(props); this.state = { list: [ {id: "1", name: "foo"}, {id: "2", name: "bar"}, {id: "3", name: "bad"}, {id: "4", name: "qux"} ], message: "" }; } dropAction(sourceId, targetId) { const { list } = this.state; const sourceName = list.find(item => item.id === sourceId).name; const targetName = list.find(item => item.id === targetId).name; this.setState({message: `${sourceName} dropped on ${targetName}`}); } render() const { list, message } = this.state; const itemType = Symbol("item"); return ( <div> <h1>{message}</h1> <ol> {list.map(item => <DnDItem id={item.id} name={item.name} type={itemType} dropAction={::this.dropAction} key={item.id}/> )} </ol> </div> ) } }
|
まとめ
これでdragされたComponentのidとdropされたComponentのidが取得できる実装が出来ました。idが取得できればあとはどうにでもできるので実際の開発に生かすことができればと思います。
今回紹介したのはReactDnDのほんの一部で他にもたくさんのオプションがあるので是非ドキュメントを読んでカスタムしてみてください。