最近在研究用 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
的介绍,这里只是作了一个基本介绍,更多的示例你们能够参考官网,本示例的代码你们在这里能够找到。