react-dnd の使い方

準備

HOC なDragDropContextで親コンポーネントをラップするか、DragDropContextProviderを親コンポーネントマークアップに含める必要があります。ただし、DragDropContextProviderは、react >= 16.0.0かつreact-dnd >= 4.0.0なバージョンである必要があります。

またブラウザ上で動作させる場合react-dnd-html5-backendというパッケージも必要です。イベントハンドラーのようなものがまとまったもので、これを使って色々解決する感じのようです。react-dndreact-dnd-html5-backendは基本同じバージョンを使うようにしましょう。

ルートとしては以下のようになります。

DragDropContextProvider

import React from 'react';
import {DragDropContextProvider} from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';

class App extends React.Component {
  render() {
    return (
      <DragDropContextProvider backend={HTML5Backend}>
        <div>なんでもいい</div>
      </DragDropContextProvider>
    );
  }
}

export default App;

DragDropContext

import React from 'react';
import {DragDropContext} from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';

class App extends React.Component {
  render() {
    return <div>なんでもいい</div>;
  }
}

export default DragDropContext(HTML5Backend)(App);

少し分かりづらいですが、this.propsの値などは増えたりしないので注意です。

Drag and Drop (dnd)

Drag イベントを設定したいならDragSource、 Drop イベントを設定したいならDropTargetHOCを使います。どちらもreact-dndから提供されています。

共通項目

どちらとも第1引数にはtypeを指定します。これは処理をつなげる為に使われます。Fooというタイプの Draggable 要素をFooというタイプの Droppable 要素へドロップした時に初めてドラッグからドロップという一連の処理が実行されます。

第3引数のconnectreact-reduxconnectと同じような動きです。react-dndで管理している状態(state)をコンポーネントのpropsへ渡す為に使います。

第2引数のspecは関数をまとめたオブジェクトを渡します。これらのキー名はreact-dndが指定している名前にする必要があり、その一部は必須項目になっています。

react-dndの関数の引数にあるpropsは HOC の対象となるコンポーネントのpropsの値、monitorsは、react-dnd.github.io/r/d/a/drag-source-monitorにある内容がオブジェクトになった形のものが入っています。
propsなどは対象先ですが、monitorsには対象元が入っているような感じです。

DragSource

specには、beginDrag(props, monitor, component), endDrag(props, monitor, component), canDrag(props, monitor), isDragging(props, monitor)が設定でき、beginDragだけは必須項目です。それぞれ、

beginDragはドラッグが始まったタイミングに発火し、endDragはドラッグが終わったタイミングに発火します。beginDragendDrag戻り値は、monitor.getItem()で取り出せるようになります。

canDragはドラッグできるかの動的制御に使います。

HOC の対象となるコンポーネントでは最小限、connectconnect.dragSource()を受け取り Drap 対象とするJSX.Elementをその関数でラップする必要があります。

import {DragSource} from 'react-dnd';

DragSource(
  'BLOCK',
  {
    beginDrag(props) {
      console.log('beginDrag');
      return props;
    }
  },
  connect => {
    return {
      connectDragSource: connect.dragSource()
    };
  }
)(
  class extends React.Component {
    render() {
      return this.props.connectDargSource(
        <div style={{style: 'orange', width: 100, height: 100}} />
      );
    }
  }
);

DropTarget

先程 Drop 側のtypeBLOCKで指定したのでコチラ側もBLOCKを指定します。

こちらのspecは、drop(props, monitor, component), hover(props, monitor, component), canDrop(props, monitor)となっており、canDropは概ねcanDragのものと同じです。
こちらには必須となっている項目はありません。

dropは Dragable 要素を Droppable 要素の上で話した時に実行され、hoverは重なっている間連続で実行されます。

またこちらでは最小限、connectconnect.dropTarget(),を受け取り Drop 対象とするJSX.Elementをその関数でラップする必要があります。

import {DropTarget} from 'react-dnd';

DropTarget(
  'BLOCK',
  {
    drop(props) {
      console.log('drop');
      // onDrop は `<Foo onDrop={() => {}} />`
      // のように渡したもの
      // props.onDrop();
    }
  },
  connect => {
    return {
      connectDrogTarget: connect.dropTarget()
    };
  }
)(
  class extends React.Component {
    render() {
      return this.props.connectDrogTarget(
        <div style={{style: 'orange', width: 100, height: 100}} />
      );
    }
  }
);

Drag も Drop もできる要素

connect.dragSource()connect.drogTarget()2つでラップしてあげればいけます。

const Draggable = DragSource(/* ... */);
const Droppable = DropTarget(/* ... */);

const DNDCompoennt = Droppable(
  Droppable(
    class extends React.Component {
      render() {
        const {connectDrogTarget, connectDragSource} = this.props;

        return connectDropTarget(
          connectDragSource(
            <div style={{style: 'orange', width: 100, height: 100}} />
          )
        );
      }
    }
  )
);

仕上げ

DragDropContextProviderより深い部分かDragDropContextを適用したコンポーネント以下の部分で使うだけです。

class App extends React.Component {
  render() {
    return (
      <DragDropContextProvider backend={HTML5Backend}>
        <DNDComponent
          onDrop={() => {
            console.log('drop');
          }}
        />
      </DragDropContextProvider>
    );
  }
}