React で好きなように Drop & Drag の実装を書ける React DnD の API が解りにくかった (以前の API の把握は react-dnd これだけ抑えておけばおk がわかり易い)のだけど、最近のバージョンアップにより useDrag / useDrop 等の React hook が提供されていて(以前の API も Legacy Decorator API として残っている)、それらを使って簡単に書けるようになっていた。
例えばよくある List を Drag して、Drop 先の要素と swap する、の最小限の実装はこんな感じ。わりと型もちゃんと書ける。
import React, { useState, useCallback, useRef, FC } from "react"
import { useDrag, useDrop, DndProvider } from "react-dnd"
import Backend from "react-dnd-html5-backend"
const DND_GROUP = "list"
interface DragItem {
type: string
index: number
}
interface ListProps {
index: number
text: string
swapList: (sourceIndex: number, targetIndex: number) => void
}
const List: FC<ListProps> = ({ index, text, swapList }) => {
const ref = useRef<HTMLLIElement>(null)
const [, drop] = useDrop({
accept: DND_GROUP,
drop(item: DragItem) {
if (!ref.current || item.index === index) {
return
}
swapList(item.index, index)
}
})
const [, drag] = useDrag({
item: { type: DND_GROUP, index }
})
drag(drop(ref))
return <li ref={ref}>{text}</li>
}
const ListView = () => {
const [list, setList] = useState(["foo", "bar", "baz", "hoge", "huga"])
const swapList = useCallback(
(sourceIndex: number, targetIndex: number) => {
[list[targetIndex], list[sourceIndex]] = [list[sourceIndex], list[targetIndex]]
setList(list.splice(0))
},
[list]
)
return (
<ul>
{list.map((text, index) => (
<List key={index} text={text} index={index} swapList={swapList} />
))}
</ul>
)
}
const App: FC = () => {
return (
<DndProvider backend={Backend}>
<ListView />
</DndProvider>
)
}
React DnD、チュートリアルで一通り学べるのだけど、題材がチェスの実装になっていて、どんな感じで Drag & Drop が実装できるのだろう、というところにたどり着くまでが長い…。