最近在研究用 React
繪製拓撲圖的時候涉及到了 HTML5
拖放 API
,瞭解到了 React DnD
這個拖放神器。React DnD
幫咱們封裝了一系列的拖放 API
,大大簡化了拖放 API
的使用方式,今天就結合下面這個示例給你們介紹下 React DnD
的用法。css
React Dnd
提供了幾個重要的 API
供咱們使用:html
DragSource
是一個高階組件,使用 DragSource
高階組件包裹的組件能夠進行拖拽操做。html5
基本用法:react
import { DragSource } from 'react-dnd' class MyComponent { /* ... */ } export default DragSource(type, spec, collect)(MyComponent)
參數:git
string
、 symbol
或者 func
,只有具備相同type類型的元素才能被 drop target
所響應。spec: 一個js對象,上面定義了一些方法,用來描述 drag source
如何對拖動事件進行響應。github
{ id: props.id }
,經過monitor.getItem()
方法能夠獲取到返回結果。monitor.didDrop()
能夠判斷 drag source
是否已經被 drop target
處理完畢。若是在 drop target
的 drop
方法中返回了一個對象,在這裏能夠經過 monitor.getDropResult()
獲取到返回結果。monitor.isDragging()
。方法中的參數解釋: - props:當前組件的 `props` 參數。 - monitor:一個 `DragSourceMonitor` 實例。經過它能夠獲取當前的拖拽信息,好比能夠獲取當前被拖拽的項目及其類型,當前和初始座標和偏移,以及它是否已被刪除。 - component:是組件的實例。使用它能夠訪問DOM元素來進行位置或大小測量,或調用組件裏面定義的方法,或者進行 `setState` 操做。有時候在 isDragging、 canDrag 方法裏可能獲取不到 `component` 這個參數,由於它們被調用時實例可能不可用。
collect: 必填項,把拖拽過程當中須要的信息注入組件的 props
,接收兩個參數 connect
和 monitor
。api
connect: DragSourceConnector
的實例,包括 dragPreview()
和 dragSource()
兩個方法,經常使用的是 dragSource()
這個方法。bash
source DOM
和 React DnD Backend
鏈接起來。DOM
節點 和 React DnD Backend
鏈接起來。DragSourceMonitor
的實例,包含的具體方法能夠參考這裏。DropTarget
是一個高階組件,被 DropTarget
包裹的組件可以放置拖拽組件,可以對 hover
或者 dropped
事件做出響應。app
基本用法:dom
import { DropTarget } from 'react-dnd' class MyComponent { /* ... */ } export default DropTarget(types, spec, collect)(MyComponent)
參數:
string
、 symbol
或者 array
,drop target
只接受具備相同 type 類型的 drag source
。spec: 一個js對象,上面定義了一些方法,描述了拖放目標對拖放事件的反應。
drag source
的 endDrag
方法裏面,調用 monitor.getDropResult()
能夠得到返回結果。drop target
的時候被調用。能夠經過 monitor.isOver({ shallow: true })
方法來檢查懸停是僅發生在當前目標上仍是嵌套上。drop target
是否接受 item。方法中的參數解釋: - props:當前組件的 `props` 參數。 - monitor:一個 `DropTargetMonitor` 實例。經過它能夠獲取當前的拖拽信息,好比能夠獲取當前被拖拽的項目及其類型,當前和初始座標和偏移,以及它是否已被刪除。 - component:是組件的實例。使用它能夠訪問DOM元素來進行位置或大小測量,或調用組件裏面定義的方法,或者進行 `setState` 操做。有時候在 isDragging、 canDrag 方法裏可能獲取不到 `component` 這個參數,由於它們被調用時實例可能不可用。
collect: 必填項,把拖拽過程當中須要的信息注入組件的 props
,接收兩個參數 connect
和 monitor
。
connect: DropTargetConnector
的實例,包括 dropTarget
一個方法。
source DOM
和 React DnD Backend
鏈接起來。DropTargetMonitor
的實例,包含的具體方法能夠參考這裏。使用 DragSource
和 DropTarget
包裹的組件必須放置在 DragDropContext
或者 DragDropContextProvider
組件內部。
基本用法:
import Backend from 'react-dnd-html5-backend' import { DndProvider } from 'react-dnd' export default function MyReactApp() { return ( <DndProvider backend={Backend}> /* your drag-and-drop application */ </DndProvider> ) }
參數:
瞭解了上述 API 的基本使用,如今咱們就來實現下開頭的demo。
本示例是基於 create-react-app 開發的,經過create-react-app的CLI工具建立咱們的demo工程:
$ create-react-app react-dnd-demo
src/index.js
import React from 'react' import ReactDOM from 'react-dom' import Container from './Container' import { DndProvider } from 'react-dnd' import Backend from 'react-dnd-html5-backend' function App() { return ( <div className="App"> <DndProvider backend={Backend}> <Container /> </DndProvider> </div> ) } const rootElement = document.getElementById('root') ReactDOM.render(<App />, rootElement)
src/Container.js
import React from 'react'; import { DropTarget } from 'react-dnd'; import DraggableBox from './DraggableBox'; import Types from './types' const styles = { width: '500px', height: '300px', position: 'relative', border: '1px solid black', } @DropTarget( Types.Box, { drop: (props, monitor, component) => { if(!component) { return; } const delta = monitor.getDifferenceFromInitialOffset(); const item = monitor.getItem(); const left = Math.round(delta.x + item.left); const top = Math.round(delta.y + item.top); component.moveBox(item.id, left, top); }, }, (connect, monitor) => ({ connectDropTarget: connect.dropTarget(), isOver: monitor.isOver(), canDrop: monitor.canDrop(), }) ) class Container extends React.Component { state = { boxes: { a: { top: 20, left: 80, title: 'Drag me around' }, b: { top: 180, left: 20, title: 'Drag me too' }, }, } moveBox = (id, left, top) => { const { boxes } = this.state; this.setState({ boxes: { ...boxes, [id]: { ...boxes[id], left, top } } }) } render() { const { isOver, canDrop, connectDropTarget} = this.props; const { boxes } = this.state; const isActive = isOver && canDrop; let backgroundColor = '#ccc'; // 拖拽組件此時正處於 drag target 區域時,當前組件背景色變爲 darkgreen if (isActive) { backgroundColor = '#453467'; } console.log('qqqq', this.state.boxes) return connectDropTarget && connectDropTarget( <div style={{ ...styles, backgroundColor}}> {Object.keys(boxes).map(item => <DraggableBox {...boxes[item]} id={item} />)} </div> ) } } export default Container;
能夠看到,在 drop
方法裏,經過 monitor.getDifferenceFromInitialOffset()
方法計算出每次 drop
的時候,當前元素與拖拽前元素位置的偏移量,monitor.getItem()
方法能夠得到當前 哪一個元素被拖拽(必需要在 drag source
的 beginDrag
方法中返回),調用 component
上的 moveBox
方法從新設置拖拽以後的最新位置,從而實現元素的移動。
collect
的 connect
方法中經過 monitor.isOver()
和 monitor.canDrop()
方法將 isOver
和 canDrop
參數傳遞到組件的 props
中來判斷當前組件是否處於拖拽狀態中,這裏能夠用來設置拖拽時容器的背景色。
這裏有個細節須要注意的是,Container 容器的 position
屬性被設置爲了 relative
,這樣被拖拽的元素就會相對於該容器進行定位。
src/DraggableBox.js
import React from 'react'; import { DragSource } from 'react-dnd'; import Box from './Box'; import Types from './types' @DragSource( Types.Box, { beginDrag: (props) => { const { id, title, left, top } = props return { id, title, left, top } } }, (connect, monitor)=> ({ connectDragSource: connect.dragSource(), isDragging: monitor.isDragging(), }) ) class DraggableBox extends React.Component { getStyle = () => { const { left, top } = this.props; const transform = `translate(${left}px, ${top}px)` return { position: 'absolute', transform, } } render() { const { connectDragSource } = this.props; return connectDragSource( <div style={this.getStyle()}><Box {...this.props}/></div> ) } } export default DraggableBox;
beginDrag
方法必須返回一個對象,之前在 drop 方法中獲取到當前被拖拽組件的信息。positon 屬性必須被設置爲 absolute,以方便相對容器進行定位。元素的移動是經過 css
的 transform
屬性進行控制的。
src/Box.js
import React from 'react'; const styles = { border: '1px dashed gray', backgroundColor: 'white', padding: '0.5rem 1rem', marginRight: '1.5rem', marginBottom: '1.5rem', cursor: 'move', display: 'inline-block' } class Box extends React.Component { render() { const { title, left, right } = this.props; return ( <div style={{...styles}}> {title} </div> ) } } export default Box;
關於 React DnD
的介紹,這裏只是作了一個基本介紹,更多的示例你們能夠參考官網,本示例的代碼你們在這裏能夠找到。