Open cisen opened 6 years ago
./packages
├── dnd-core
├── react-dnd
├── react-dnd-html5-backend
└── react-dnd-test-backend
对应逻辑结构是这样:
API 接React
react-dnd 定义Context,提供Provider、Container factory等上层API
-------
Core 抽象(定义interface)
dnd-core 定义Action、Reducer,连接上下层
-------
Backends 接native,封装DnD特性(实现interface)
react-dnd-xxx-backend 接具体环境,通过Dispatch Action把native DnD状态传递到上层
可以看作基于Redux的逻辑拆解,中间层Core持有DnD状态,下层Backends负责实现约定的interface,作为Core的数据源,上层API从Core取出状态并传递给业务层
给App根组件声明DragDropContext,例如:
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
class App extends Component {}
export default DragDropContext(HTML5Backend)(App);
DragSource高阶组件接受3个参数(type,spec和collect()),例如:
export const ItemTypes = {
KNIGHT: 'knight'
};
const knightSpec = {
beginDrag(props) {
// 定义Item结构,通过monitor.getItem()读取
return {
pieceId: props.id
};
}
};
function collect(connector, monitor) {
return {
connectDragSource: connectorconnector.dragSource(),
isDragging: monitor.isDragging()
}
}
最后与Component/Container连接起来(像Redux connect()一样):
export default DragSource(ItemTypes.KNIGHT, knightSpec, collect)(Knight);
组件拿到注入的DnD状态渲染对应UI,例如:
render() {
const { connectDragSource, isDragging } = this.props;
return connectDragSource(
<div style={{
opacity: isDragging ? 0.5 : 1,
cursor: 'move'
}} />
);
}
很自然地实现了被拖走的效果(拖放对象变成半透明),看不到复杂的DnD处理逻辑(这些都被封装到了React DnD Backend,仅暴露出业务需要的DnD状态)
同样需要3个参数(type,spec和collect()):
const dropSpec = {
canDrop(props) {
return canMoveKnight(props.x, props.y);
},
drop(props, monitor) {
const { id } = monitor.getItem();
moveKnight(id, props.x, props.y);
}
};
function collect(connector, monitor) {
return {
connectDropTarget: connector.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
};
}
最后连接起来:
export default DropTarget(ItemTypes.KNIGHT, dropSpec, collect)(BoardSquare);
组件取这些注入的DnD状态来展示对应的UI,例如:
render() {
const { connectDropTarget, isOver, canDrop } = this.props;
return connectDropTarget(
<div>
{isOver && !canDrop && this.renderOverlay('red')}
{!isOver && canDrop && this.renderOverlay('yellow')}
{isOver && canDrop && this.renderOverlay('green')}
</div>
);
}
坑根据拖动操作合法性变色的效果也实现了,看起来同样很自然
浏览器DnD默认会根据被拖动的元素创建drag preview(一般像个半透明截图),需要定制的话,与DragSource的创建方式类似:
function collect(connector, monitor) {
return {
connectDragSource: connector.dragSource(),
connectDragPreview: connector.dragPreview()
}
}
通过注入的connectDragPreview()来定制DragPreview,接口签名与connectDragSource()一致,都是dragPreview() => (elementOrNode, options?),例如常见的拖动抓手(handle)效果可以这样实现:
render() {
const { connectDragSource, connectDragPreview } = this.props;
return connectDragPreview(
<div
style={{
position: 'relative',
width: 100,
height: 100,
backgroundColor: '#eee'
}}
>
Card Content
{connectDragSource(
<div
style={{
position: 'absolute',
top: 0,
left: '100%'
}}
>
<HANDLE>
</div>
)}
</div>
);
}
另外,还可以把Image对象作为DragPreview(IE不支持):
componentDidMount() {
const img = new Image();
img.src = 'http://mysite.com/image.jpg';
img.onload = () => this.props.connectDragPreview(img);
}
术语解析
Backend
HTML5 DnD API兼容性不怎么样,并且不适用于移动端,所以干脆把DnD相关具体DOM事件抽离出去,单独作为一层,即Backend:
Item和Type
Item是对元素/组件的抽象理解,拖放的对象不是DOM元素或React组件,而是特定数据模型(Item):
进行这种抽象同样是为了解耦:
Type与Item的关系类似于Class与Class Instance,Type作为类型标识符用来表示同类Item:
Type作为萝卜(drag source)和坑(drop target)的匹配依据,相当于经典DnD库的group name
collect
collect 是一个函数,默认有两个参数:connect 和 monitor。collect函数将返回一个对象,这个对象会注入到组件的 props 中,也就是说,我们可以通过 this.props 获取collect返回的所有属性。
参数 connect
source组件 collect 中 connect是 DragSourceConnector的实例,它内置了两个方法:dragSource() 和 dragPreview()。dragSource()返回一个方法,将source组件传入这个方法,可以将 source DOM 和 React DnD backend 连接起来;dragPreview() 返回一个方法,你可以传入节点,作为拖拽预览时的角色。 target组件 collect 中 connect是 DropTargetConnector的实例,内置的方法 dropTarget() 对应 dragSource(),返回可以将 drop target 和 React DnD backend 连接起来的方法。
参数 monitor
monitor 用于查询当前的拖拽状态,其对应实例内置了很多方法。
source组件 collect 中 monitor是 DragSourceMonitor的实例。 target组件 collect 中 monitor是 DropTargetMonitor的实例。
Monitor是拖放状态的集合,比如拖放操作是否正在进行,是的话萝卜是哪个坑是哪个:
例如:
以props注入的方式暴露DnD内部状态,类似于Redux的mapStateToProps:
P.S.事实上,React DnD就是基于Redux实现的,见下文核心实现部分
Connector
Connector用来建立DOM抽象(React)与DnD Backend需要的具体DOM元素之间的联系:
用法很有意思:
建立联系的部分connectDropTarget(
)看起来相当优雅,猜测实际作用应该相当于:猜对了:
Drag Source与Drop Target
上面提到过这两个东西,可以称之为DnD Role,表示在DnD中所饰角色,除了drag source和drop target外,还有一个叫drag preview,一般可以看作另一种状态的drag source DnD Role是React DnD中的基本抽象单元:
是该角色相关描述及动作的集合,包括Type,DnD Event Handler(例如drop target通常需要处理hover、drop等事件)等
API
顶级API
想要灵活使用,就先知道几个核心API
Connecting to DOM
DragSourceConnector