Help us understand the problem. What is going on with this article?

react-dnd これだけ抑えておけばおk

More than 1 year has passed since last update.

巷で難しい難しいと言われていた react-dnd でドラッグアンドドロップを実装してみたところ、案外楽だったのでまとめる。

用語整理

  • DragSource : マウスで掴んでドラッグする対象
  • DropTarget : ドロップ対象
  • monitor : drop/hover時に今現在のイベント対象の状態を取り出せるもの

使い方

  • @DragDropContext を ドラッグアンドドロップしたい領域のコンポーネントに注入する
  • @DropTarget を落としたい先のコンポーネントに注入する
  • @DragSource をドラッグさせたいコンポーネントに注入する

たとえばドラッグアンドドロップでリストの要素を入れ替えたい場合、 リスト全体が DragDropContext で、 リスト要素が DragSource かつ DropTarget になる。

実装例

並び替えられるリスト要素の実装

@DropTarget("item", {
  // drop 時のコールバック
  drop(dropProps, monitor, dropComponent) {
    const dragProps = monitor.getItem(); // DragSource の props が取り出せる
    if (dropProps.id !== dragProps.id) {
      dragProps.onDrop(dragProps.id, dropProps.id);
    }
  }
}, connect => {
  return {
    connectDropTarget: connect.dropTarget()
  };
})
@DragSource("item", {
  beginDrag(props) {
    return props;
  }
}, (connect, monitor) => {
  return {
    connectDragSource: connect.dragSource(),
    isDragging: monitor.isDragging()
  };
})
class DragItem extends Component {
  render() {
    return this.props.connectDragSource(this.props.connectDropTarget(
      <div>
        {this.props.children}
      </div>
    ));
  }
}

リストの実装

@DragDropContext(ReactDnDHTML5Backend)
export default class SortableList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      items: [
        {id: 1, name: "aaa"},
        {id: 2, name: "bbb"},
        {id: 3, name: "ccc"}
      ];
    }
  }
  render() {
    return (
      <div>
        {
          this.state.items.map(item => {
            return <DragItem
              key={item.id}
              id={item.id}
              onDrop={
                (toId, fromId) => {
                  // ここで入れ替える処理をする
                  const items = this.state.items.slice();
                  const toIndex = items.findIndex(i => i.id === toId);
                  const fromIndex = items.findIndex(i => i.id === fromId);
                  const toItem = items[toIndex];
                  const fromItem = items[fromIndex];
                  items[toIndex] = fromItem;
                  items[fromIndex] = toItem;
                  this.setState({items})
                }
              }
            >
              {item.name}
            </DragItem>

          })
        }
      </div>
    );
  }
}

この実装例だと onDrop コールバックを与えて、from, to を取得し、state.items の中身を入れ替える。flux だったら store に向かって投げるだろう。


追記: 久しぶりに見直したらまだ便利だったので、TypeScript版も書いておく。デコレータは使わない。正直、型はうまくつかない。

import React from "react"
import { DragDropContext, DragSource, DropTarget } from "react-dnd"
import ReactDnDHTML5Backend from "react-dnd-html5-backend"

const DND_GROUP = "sortable"
type Item = { id: string; name: string }

const SortableList = DragDropContext(ReactDnDHTML5Backend)(
  class SortableListImpl extends React.Component<any, { items: Item[] }> {
    constructor(props: any) {
      super(props)
      this.state = {
        items: [
          { id: "1", name: "aaa" },
          { id: "2", name: "bbb" },
          { id: "3", name: "ccc" }
        ]
      }
    }
    render() {
      return (
        <div>
          {this.state.items.map(item => {
            return (
              <DraggableItem
                key={item.id}
                id={item.id}
                onDrop={(toId: string, fromId: string) => {
                  // ここで入れ替える処理をする
                  const items = this.state.items.slice()
                  const toIndex = items.findIndex(i => i.id === toId)
                  const fromIndex = items.findIndex(i => i.id === fromId)
                  const toItem = items[toIndex]
                  const fromItem = items[fromIndex]
                  items[toIndex] = fromItem
                  items[fromIndex] = toItem
                  this.setState({ items })
                }}
              >
                {item.name}
              </DraggableItem>
            )
          })}
        </div>
      )
    }
  }
)

const DraggableItem: React.ComponentType<{ id: string; onDrop: any }> = compose(
  DropTarget<Item>(
    DND_GROUP,
    {
      // drop 時のコールバック
      drop(dropProps, monitor, _dropComponent) {
        if (monitor) {
          const dragProps: {
            key: string
            id: string
            onDrop: (from: string, to: string) => void
          } = monitor.getItem() as any // DragSource の props が取り出せる
          if (dropProps.id !== dragProps.id) {
            dragProps.onDrop(dragProps.id, dropProps.id)
          }
        }
      }
    },
    connect => {
      return {
        connectDropTarget: connect.dropTarget()
      }
    }
  ),
  DragSource<Item>(
    DND_GROUP,
    {
      beginDrag(props) {
        return props
      }
    },
    (connect, monitor) => {
      return {
        connectDragSource: connect.dragSource(),
        isDragging: monitor.isDragging()
      }
    }
  )
)((props: any) => {
  return props.connectDragSource(
    props.connectDropTarget(<div>{props.children}</div>)
  )
}) as any

function compose(...funcs: Function[]) {
  return funcs.reduce((a, b) => (...args: any) => a(b(...args)));
}
plaid
CXプラットフォーム「KARTE」の開発・運営、EC特化型メディア「Shopping Tribe」の企画・運営、CX特化型メディア「XD(クロスディー)」の企画・運営
https://plaid.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした