简介

这个库恰好最近看到别人的分析比较多,觉得也比较值得看一下

拖拽库

  • react-dnd 除了列表操作,还有拖拽进去,随意拖拽某个位置,
  • react-beautiful-dnd 专注于列表拖拽 貌似是后起之秀
  • react-draggable 容器内位置的拖拽

Sortable

React Dnd

核心概念

https://react-dnd.github.io/react-dnd/docs/overview

  • item是看到可拖拽的对象,原始就是一个js的对象,而不是dom或其他
  • type即定义类型用来区分不同的drag和drop,以及drag和drop的匹配
  • monitor 即保存了拖拽对象是否拖拽中状态,以及拖放容器状态,并暴露给组件来响应state的变化
  • Connectors?这是啥?
  • Drag Sources and Drop Targets 比较弱化了
  • Backends 不同的拖砖容器,有html5或touch
  • Hooks vs Higher-Order Components Decorator

提供API的模式

Hooks
Decorator

思考这两者的特性

核心的对象monitor

  • DragSourceMonitor
  1. canDrag
  2. isDragging
  3. getItemType
  4. getItem 当前拖拽的
  5. getDropResult
  6. getClientOffset
  • DropTargetMonitor
  1. canDrop
  2. isOver
  3. getItemType
  4. getItem 当前拖拽的对象
  5. getDropResult
  • DragLayerMonitor

如何分层的 DragDropManager

backend ->
store -> redux的store,reducers和actions
monitor -> SourceMonitor, TargetMonitor
manager -> DragDropManager
Connector -> SourceConnector,TargetConnector

背后的核心dragdropManager

  1. export function createDragDropManager(
  2. backendFactory: BackendFactory,
  3. globalContext: unknown = undefined,
  4. backendOptions: unknown = {},
  5. debugMode = false,
  6. ): DragDropManager {
  7. const store = makeStoreInstance(debugMode)
  8. const monitor = new DragDropMonitorImpl(store, new HandlerRegistryImpl(store))
  9. const manager = new DragDropManagerImpl(store, monitor)
  10. const backend = backendFactory(manager, globalContext, backendOptions)
  11. manager.receiveBackend(backend)
  12. return manager
  13. }
  14. // 1 store, 即redux定义的store,在reducers中的数据,如下的state
  15. // 即整个store
  16. export interface State {
  17. dirtyHandlerIds: DirtyHandlerIdsState
  18. dragOffset: DragOffsetState // 当前拖拽的
  19. refCount: RefCountState
  20. dragOperation: DragOperationState
  21. stateId: StateIdState
  22. }
  23. // 2 monitor 提供访问当前拖拽状态的对象,拖拽的对象,拖拽的位置等
  24. // registry, drag, drop实例都会注册到registry属性
  25. // 背后通过访问registry以及store,来获取各信息,也就是个中间商,封装简化对内部store中数据的访问,
  26. // 3 actions 即redux中的action,抽象了各种操作的actions,beginDrag,endDrag,drop等,然后action会处理数据逻辑
  27. // 最终返回payload,通过dispatch更新到store中
  28. // 4 backend 即html5等backend,抽象了底层各平台的细节差异,
  29. // 以html5-backend为例
  30. //

有哪些设计模式等学习的

beginDrag中的createBeginDrag的factory工厂模式

整个流程是如何的?如果你拖动了一个元素会发生什么?

connectDragSource(sourceId, node, options) -> handleDragStart handleSelectStart

1 handleDragStart dragStartSourceIds.unshift(sourceId)
2 this.actions.beginDrag(dragStartSourceIds) 这背后做了很多逻辑
3

handleTopDragStart 即window上绑定的事件

  1. export class HTML5BackendImpl implements Backend {
  2. // React-Dnd Components
  3. private actions: DragDropActions
  4. private monitor: DragDropMonitor
  5. private registry: HandlerRegistry
  6. private sourcePreviewNodes: Map<string, Element> = new Map()
  7. private sourceNodes: Map<string, Element> = new Map()
  8. private dragStartSourceIds: string[] | null = null
  9. private dropTargetIds: string[] = []
  10. private currentDragSourceNode: Element | null = null
  11. public connectDragSource(
  12. sourceId: string,
  13. node: Element,
  14. options: any,
  15. ): Unsubscribe {
  16. this.sourceNodes.set(sourceId, node)
  17. this.sourceNodeOptions.set(sourceId, options)
  18. const handleDragStart = (e: any) => this.handleDragStart(e, sourceId)
  19. const handleSelectStart = (e: any) => this.handleSelectStart(e)
  20. node.setAttribute('draggable', '' + this.monitor.canDragSource(sourceId))
  21. node.addEventListener('dragstart', handleDragStart)
  22. node.addEventListener('selectstart', handleSelectStart)
  23. // 返回解绑的方法
  24. return ...
  25. }
  26. public handleTopDragStart = (e: DragEvent): void => {
  27. // Don't publish the source just yet (see why below)
  28. this.actions.beginDrag(dragStartSourceIds || [], {
  29. publishSource: false,
  30. getSourceClientOffset: this.getSourceClientOffset,
  31. clientOffset,
  32. })
  33. if (this.monitor.isDragging()) {
  34. if (dataTransfer && typeof dataTransfer.setDragImage === 'function') {
  35. const sourceId: string = this.monitor.getSourceId() as string
  36. const sourceNode = this.sourceNodes.get(sourceId)
  37. const dragPreview = this.sourcePreviewNodes.get(sourceId) || sourceNode
  38. ...
  39. }
  40. }
  41. }
  42. }

React Beautiful Dnd

特征?

性能高?虚拟
简洁的API
强大的场景和功能

核心概念

  • DragDropContext 核心的manager,onDragEnd
    • 一个DragDropContext里可以有多个Droppable
  • Droppable
    • provided,snapshot,droppableId
  • Draggable
    • provided,snapshot,draggableId

https://codesandbox.io/s/ql08j35j3q 多个Droppable,即两列进行拖拽
https://codesandbox.io/s/k260nyxq9v 最简单的入门demo,可拖拽排序的垂直列表
https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/about/design-principles.md 设计原则

为什么有了react-dnd,还做了这个?

react-beautiful-dnd is a higher level abstraction specifically built for lists (vertical, horizontal, movement between lists, nested lists and so on)

React Dnd VS React Beautiful Dnd

DnD Beautiful Dnd
核心概念
- DndProvider
- Drag
- Drop 通过此类处理事件
- DragDropManager 背后隐藏的

- DragDropContext 核心的manager,通过此来处理事件
- Draggable Component
- Droppable Component
暴露方式 hooks或hoc render props

drop对象处理 顶层context处理
排序移动处理方式 拖拽排序需要自己写很多处理逻辑,位置判断等,有点偏底层。概念用drop也不太好理解

排序的example
onDragEnd只暴露startIndex, endIndex,自己处理移动的数据操作

两个不同的设计思想

React Draggable

借鉴

http://www.ayqy.net/blog/react-dnd/

https://medium.com/@alexandereardon/rethinking-drag-and-drop-d9f5770b4e6b
https://www.npmtrends.com/react-beautiful-dnd-vs-react-dnd-vs-react-draggable

example
https://codesandbox.io/s/github/react-dnd/react-dnd/tree/gh-pages/examples_hooks_js/01-dustbin/single-target?from-embed=&file=/src/Box.jsx