React DnD"/>
React DnD
简介
最近在研究用 React
绘制拓扑图的时候涉及到了 HTML5
拖放 API
,了解到了 React DnD
这个拖放神器。React DnD
帮我们封装了一系列的拖放 API
,大大简化了拖放 API
的使用方式,今天就结合下面这个示例给大家介绍下 React DnD
的用法。
重要概念
React Dnd
提供了几个重要的 API
供我们使用:
- DragSource
- DropTarget
- DragDropContext && DragDropContextProvider
DragSource
DragSource
是一个高阶组件,使用 DragSource
高阶组件包裹的组件可以进行拖拽操作。
基本用法:
import { DragSource } from 'react-dnd'class MyComponent {/* ... */
}export default DragSource(type, spec, collect)(MyComponent)
参数:
- type:指定拖拽元素的类型,值的类型可以是
string
、symbol
或者func
,只有具有相同type类型的元素才能被drop target
所响应。 -
spec: 一个js对象,上面定义了一些方法,用来描述
drag source
如何对拖动事件进行响应。- beginDrag(props, monitor, component): 必填项。当拖拽开始的时候,这个方法就会被调用,这个方法必须要返回一个js 对象来描述被拖拽的元素,比如返回一个
{ id: props.id }
,通过monitor.getItem()
方法可以获取到返回结果。 - endDrag(props, monitor, component): 非必填项。当拖拽停止的时候,这个方法会被调用,通过
monitor.didDrop()
可以判断drag source
是否已经被drop target
处理完毕。如果在drop target
的drop
方法中返回了一个对象,在这里可以通过monitor.getDropResult()
获取到返回结果。 - canDrag(props, monitor): 可选参数。可以指定当前的拖拽操作是否被允许。
- isDragging(props, monitor): 可选参数。拖拽时触发的事件,注意,在这个方法里面不能再调用
monitor.isDragging()
。
- beginDrag(props, monitor, component): 必填项。当拖拽开始的时候,这个方法就会被调用,这个方法必须要返回一个js 对象来描述被拖拽的元素,比如返回一个
方法中的参数解释:
- props:当前组件的 `props` 参数。
- monitor:一个 `DragSourceMonitor` 实例。通过它可以获取当前的拖拽信息,比如可以获取当前被拖拽的项目及其类型,当前和初始坐标和偏移,以及它是否已被删除。
- component:是组件的实例。使用它可以访问DOM元素来进行位置或大小测量,或调用组件里面定义的方法,或者进行 `setState` 操作。有时候在 isDragging、 canDrag 方法里可能获取不到 `component` 这个参数,因为它们被调用时实例可能不可用。
-
collect: 必填项,把拖拽过程中需要的信息注入组件的
props
,接收两个参数connect
和monitor
。-
connect:
DragSourceConnector
的实例,包括dragPreview()
和dragSource()
两个方法,常用的是dragSource()
这个方法。- dragSource: 返回一个函数,传递给组件用来将
source DOM
和React DnD Backend
连接起来。 - dragPreview: 返回一个函数,传递给组件用来将拖动时预览的
DOM
节点 和React DnD Backend
连接起来。
- dragSource: 返回一个函数,传递给组件用来将
- monitor:
DragSourceMonitor
的实例,包含的具体方法可以参考这里。
-
DropTarget
DropTarget
是一个高阶组件,被 DropTarget
包裹的组件能够放置拖拽组件,能够对 hover
或者 dropped
事件作出响应。
基本用法:
import { DropTarget } from 'react-dnd'class MyComponent {/* ... */
}export default DropTarget(types, spec, collect)(MyComponent)
参数:
- types: 指定拖拽元素的类型,值的类型可以是
string
、symbol
或者array
,drop target
只接受具有相同 type 类型的drag source
。 -
spec: 一个js对象,上面定义了一些方法,描述了拖放目标对拖放事件的反应。
- drop(props, monitor, component): 可选参数。当item被放置到目标元素上时会被调用。如果这个方法返回的是一个js对象,在
drag source
的endDrag
方法里面,调用monitor.getDropResult()
可以获得返回结果。 - hover(props, monitor, component): 可选参数。当item经过
drop target
的时候被调用。可以通过monitor.isOver({ shallow: true })
方法来检查悬停是仅发生在当前目标上还是嵌套上。 - canDrop(props, monitor): 可选参数。这个方法可以用来检测
drop target
是否接受 item。
- drop(props, monitor, component): 可选参数。当item被放置到目标元素上时会被调用。如果这个方法返回的是一个js对象,在
方法中的参数解释:
- props:当前组件的 `props` 参数。
- monitor:一个 `DropTargetMonitor` 实例。通过它可以获取当前的拖拽信息,比如可以获取当前被拖拽的项目及其类型,当前和初始坐标和偏移,以及它是否已被删除。
- component:是组件的实例。使用它可以访问DOM元素来进行位置或大小测量,或调用组件里面定义的方法,或者进行 `setState` 操作。有时候在 isDragging、 canDrag 方法里可能获取不到 `component` 这个参数,因为它们被调用时实例可能不可用。
-
collect: 必填项,把拖拽过程中需要的信息注入组件的
props
,接收两个参数connect
和monitor
。-
connect:
DropTargetConnector
的实例,包括dropTarget
一个方法。- dropTarget: 返回一个函数,传递给组件用来将
source DOM
和React DnD Backend
连接起来。
- dropTarget: 返回一个函数,传递给组件用来将
- monitor:
DropTargetMonitor
的实例,包含的具体方法可以参考这里。
-
DragDropContext && DragDropContextProvider
使用 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>)
}
参数:
- backend: 必填项。HTML5 DnD API 兼容性不怎么样,并且不适用于移动端,所以干脆把 DnD 相关具体DOM事件抽离出去,单独作为一层,即 Backend,我们可以根据 React DnD提供的约定协议定义自己的 Backend。
示例
了解了上述 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 区域时,当前组件背景色变为 darkgreenif (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 } = propsreturn { 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
的介绍,这里只是做了一个基本介绍,更多的示例大家可以参考官网,本示例的代码大家在这里可以找到。
更多推荐
React DnD
发布评论