React DnD 的英文是 Drag and Drop for React。
React DnD 是 React 和 Redux 的核心作者 Dan Abramov 创造的一组 React 高阶组件,基于数据而不是视图开发的一套拖拽工具,主要使用typescript进行开发。
基础概念和使用示例可以参考://www.greatytc.com/p/8a1e16d5519b和官方tutorial。
作者基于HTML5提供了一个backend实现,相关api参考:HTML_Drag_and_Drop_API。
React DnD 的基本概念
Manager
Manager是DnD中非常重要的角色,内部保存了store、monitor和backend,Manager作为入口,对外暴露三者,会订阅store,并调用Backend的setup(负责注册拖拽相关一系列的事件)或teardown(销毁拖拽事件)。
Backends
React DnD 抽象了后端的概念,我们可以使用 HTML5 拖拽后端,也可以自定义 touch、mouse 事件模拟的后端实现,后端主要用来抹平浏览器差异,处理 DOM 事件,同时把 DOM 事件转换为 React DnD 内部的 redux action。主要注册了拖拽相关的监听器,对外提供了dom(drag、preview和drop)注入方法。
Item
React DnD 基于数据驱动,当拖放发生时,它用一个数据对象来描述当前的元素,比如 { cardId: 25 }。
Type
类型是唯一标识应用程序中整个项目类别的字符串(或符号),类似于 redux 里面的 actions types 枚举常量。
Monitors
拖放操作都是有状态的,React DnD 通过 Monitor 来存储这些状态并且提供查询。
Connectors
Backend 关注 DOM 事件,组件关注拖放状态,connector 可以连接组件和 Backend ,可以让 Backend 获取到 DOM。
useDrag
用于将当前组件用作拖动源的钩子。
import { useDrag } from 'react-dnd' function DraggableComponent(props) { const [collectedProps, drag] = useDrag({ item: { id, type } }) return <div ref={drag}>...</div> }
useDrop
使用当前组件作为放置目标的钩子。
const [collectedProps, drop] = useDrop({ accept }) return <div ref={drop}>Drop Target</div> }
架构与源码
上图说明:
一、DndProvider
根据createDragDropManager(backend, context)创建名为dragDropManager的单例,并通过React.createContext共享,包含有store、monitor和backend信息;
二、useDrag
2.1、通过useDragSourceMonitor生成DragSourceMonitorImpl实例monitor
1、通过useDragDropManager方法(本质就是React.useContext)获取dragDropManager单例;
2、通过DragSourceMonitorImpl(dragDropManager)生成实例,内部维护了一些诸如canDrag、isDragging、didDrop等常用操作,本质是通过dragDropManager单例内部的manager实现。
2.2、通过useDragSourceConnector生成SourceConnector实例connector
1、通过useDragDropManager获取manager,再通过manager.getBackend()获取backend;
2、通过SourceConnector(backend)生成connector,然后将DragSourceOptions、dragPreviewOptions分别保存在connector.dragSourceOptions、connector.dragPreviewOptions,并通过connector.reconnect()重连;connector内部维护了dragSourceRef、dragSourceNode、dragPreviewRef和dragPreviewNode等数据,还会通过调用backend.connectDragSource和backend.connectDragPreview方法,将drag和preivew相关信息传入backend。
2.3、通过useRegisteredDragSource(spec, monitor, connector)注册DragSource
1、通过DragSourceImpl(spec, monitor, connector)生成DragSource实例handler,提供了beginDrag、canDrag、isDragging和endDrag方法,四个方法基本是调用spec中配置的相应方法;
2、调用manger的registry将handler注册到manager中(manager.getRegitry().addSource),生成对应的handlerId,通过monitor和connector的receiveHandlerId方法注入到各自里面。
2.4、通过useCollectedProps(spec.collect, monitor, connector)返回collect数据
通过monitor.subscribeToStateChange方法注册监听方法updateCollected,该方法会通过collect(monitor)获取到最新的结果
2.5、dragRef通过useConnectDragSource(connector)返回dragRef
通过执行connector.dragSource方法获取最新的drag ref,并reconnectDragSource。
2.6、通过useConnectDragPreview(connector)返回dragPreview
类似2.5。
三、useDrop
四、Backend(HTML5Backend)