事件 | 触发时刻 |
---|---|
dragstart | 当用户开始拖拽一个元素时触发 |
dragover | 当元素被拖到一个可释放目标上时触发(每100毫秒触发一次) |
dragend | 当拖拽操作结束时触发 |
事件 | 触发时刻 |
---|---|
touchstart | 触摸开始 |
touchmove | 接触点移动 |
touchend | 触摸结束 |
useState
通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 stateuseCallback
把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新useMemo
把创建函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值useLayoutEffect
给函数组件增加了操作副作用的能力const NewComponent = higherOrderComponent(OldComponent)
backend
属性注入一个后端拖动预览
。 当光标移动时,您不必进行任何绘图,这很方便{type:'card', id: 'card1' }
, 将拖动的数据描述为普通对象可以帮助您保持组件解耦卡片
类型DragSourceMonitor
是传递给基于钩子或基于装饰器的拖动源的收集函数的对象beginDrag()
方法返回一个对象来指定它refs
npm install react-dnd react-dnd-html5-backend --save
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Container from './Container';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
ReactDOM.render(
<DndProvider backend={HTML5Backend}>
<Container />
</DndProvider>,
document.getElementById('root')
);
src\Container.js
import React from 'react'
const style = {
width: '300px'
}
function Container() {
return (
<div style={style}>
Container
</div>
)
}
export default Container;
src\Card.js
import React from 'react'
const style = {
backgroundColor: 'red',
padding: '5px',
margin: '5px',
border: '1px dashed gray',
cursor: 'move'
}
export default function Card({ text }) {
return (
<div style={style}>
{text}
</div>
)
}
+import React, { useState } from 'react'
+import Card from './Card';
const style = {
width: '300px'
}
function Container() {
+ const [cards, setCards] = useState([
+ { id: 'card1', text: '卡片1' },
+ { id: 'card2', text: '卡片2' },
+ { id: 'card3', text: '卡片3' }
+ ]);
return (
<div style={style}>
+ {
+ cards.map((card, index) => (
+ <Card key={card.id} text={card.text} />
+ ))
+ }
</div>
)
}
export default Container;
src\ItemTypes.js
export const CARD = 'card';
/**
* React DnD 使用数据而不是视图作为来源
* 当您在屏幕上拖动某物时,我们并不是说正在拖动组件或 DOM 节点。 相反,我们说某种类型的项目正在被拖动
* 什么是项目? 一个项目是一个简单的 JavaScript 对象,描述被拖动的内容
* 那什么是type(类型)呢? 类型是一个字符串(或Symbol),它唯一地标识了应用程序中的项目。 在看板应用程序中,您可能有一个代表可拖动卡片的卡片类型
* 类型很有用,因为随着您的应用程序的增长,您可能希望使更多内容可拖动,但您不一定希望所有现有的放置目标突然开始对新项目做出反应
* 这些类型允许您指定兼容的拖放源和放置目标
*/
src\Container.js
import React, { useState } from 'react'
import Card from './Card';
const style = {
width: '200px'
}
function Container() {
const [cards, setCards] = useState([
{ id: 'card1', text: '卡片1' },
{ id: 'card2', text: '卡片2' },
{ id: 'card3', text: '卡片3' }
]);
return (
<div style={style}>
{
cards.map((card, index) => (
+ <Card key={card.id} text={card.text} id={card.id} index={index} />
))
}
</div>
)
}
export default Container;
src\Card.js
import React, { useRef } from 'react'
+import { useDrag } from 'react-dnd';
+import { CARD } from './ItemTypes';
const style = {
backgroundColor: 'red',
padding: '5px',
margin: '5px',
border: '1px dashed gray',
cursor: 'move'
}
export default function Card({ text, id, index }) {
+ const ref = useRef(null);
+ //useDrag hook 提供了一种将组件作为拖动源连接到 DnD 系统的方法
+ //Collected Props: 包含从 collect 函数收集的属性的对象。 如果没有定义 collect 函数,则返回一个空对象
+ //DragSource Ref: 拖动源的连接器功能。 这必须附加到 DOM 的可拖动部分
+ const [{ isDragging }, drag] = useDrag({//spec
+ //必需的。 这必须是字符串或Symbol。 只有为相同类型注册的放置目标才会对此项目做出反应
+ type: CARD,
+ //item: 必需的 (对象或者函数) 当这是一个对象时,它是一个描述被拖动数据的普通 JavaScript 对象
+ item: () => ({ id, index }),
+ //collect: 收集功能。 它应该返回一个普通的属性对象,以返回以注入到您的组件属性中
+ //它接收两个参数,monitor 和 props
+ collect: (monitor) => ({//要收集的属性
+ isDragging: monitor.isDragging()
+ })
+ });
+ const opacity = isDragging ? 0.1 : 1;
+ drag(ref)
return (
+ <div ref={ref} style={{ ...style, opacity }}>
{text}
</div>
)
}
src\Container.js
import React, { useState } from 'react'
import Card from './Card';
const style = {
width: '200px'
}
function Container() {
const [cards, setCards] = useState([
{ id: 'card1', text: '卡片1' },
{ id: 'card2', text: '卡片2' },
{ id: 'card3', text: '卡片3' }
]);
+ const moveCard = (dragIndex, hoverIndex) => {
+ const dragCard = cards[dragIndex];
+ const cloneCards = [...cards];
+ cloneCards.splice(dragIndex, 1);
+ cloneCards.splice(hoverIndex, 0, dragCard);
+ setCards(cloneCards);
+ }
return (
<div style={style}>
{
cards.map((card, index) => (
+ <Card key={card.id} text={card.text} id={card.id} index={index} moveCard={moveCard} />
))
}
</div>
)
}
export default Container;
src\Card.js
import React, { useRef } from 'react'
+import { useDrag, useDrop } from 'react-dnd';
import { CARD } from './ItemTypes';
const style = {
backgroundColor: 'red',
padding: '5px',
margin: '5px',
border: '1px dashed gray',
cursor: 'move'
}
export default function Card({ text, id, index, moveCard }) {
const ref = useRef(null);
+ //useDrop hook 为您提供了一种将组件连接到 DnD 系统作为放置目标的方法
+ // Collected Props: 包含从 collect 函数收集的属性的对象
+ //DropTarget Ref: 放置目标的连接器函数。 这必须附加到 DOM 的放置目标部分
+ const [, drop] = useDrop({
+ //一个字符串或一个Symbol,这个放置目标只会对指定类型的拖拽源产生的项目做出反应
+ accept: CARD,
+ //收集功能。 它应该返回一个普通的属性对象,以返回以注入到您的组件属性中
+ collect: (monitor) => ({}),
+ //当在组件发生hover事件时调用
+ hover(item, monitor) {
+ //被拖动卡片的索引
+ const dragIndex = item.index;
+ //hover卡片的索引
+ const hoverIndex = index;
+ //如果一样什么都不做
+ if (dragIndex === hoverIndex) {
+ return;
+ }
+ //获取hover卡片的位置信息
+ const { top, bottom } = ref.current.getBoundingClientRect();
+ //获取hover卡片高度的一半
+ const halfOfHoverHeight = (bottom - top) / 2;
+ //获取鼠标最新的X和Y坐标
+ const { y } = monitor.getClientOffset();
+ const hoverClientY = y - top;
+ if ((dragIndex < hoverIndex && hoverClientY > halfOfHoverHeight)
+ || (dragIndex > hoverIndex && hoverClientY < halfOfHoverHeight)) {
+ moveCard(dragIndex, hoverIndex);
+ item.index = hoverIndex;
+ }
+ }
+ });
//useDrag hook 提供了一种将组件作为拖动源连接到 DnD 系统的方法
//Collected Props: 包含从 collect 函数收集的属性的对象。 如果没有定义 collect 函数,则返回一个空对象
//DragSource Ref: 拖动源的连接器功能。 这必须附加到 DOM 的可拖动部分
const [{ isDragging }, drag] = useDrag({//spec
//必需的。 这必须是字符串或Symbol。 只有为相同类型注册的放置目标才会对此项目做出反应
type: CARD,
//item: 必需的 (对象或者函数) 当这是一个对象时,它是一个描述被拖动数据的普通 JavaScript 对象
item: () => ({ id, index }),
//collect: 收集功能。 它应该返回一个普通的属性对象,以返回以注入到您的组件属性中
//它接收两个参数,monitor 和 props
collect: (monitor) => ({//要收集的属性
isDragging: monitor.isDragging()
})
});
const opacity = isDragging ? 0.1 : 1;
drag(ref)
+ drop(ref)
return (
<div ref={ref} style={{ ...style, opacity }}>
{text}
</div>
)
}
src\react-dnd\index.js
export * from './core'
src\react-dnd\core\index.js
export { default as DndContext } from './DndContext';
export { default as DndProvider } from './DndProvider';
src\react-dnd\core\DndContext.js
import React from 'react';
const DndContext = React.createContext({});
export default DndContext;
src\react-dnd\core\DndProvider.js
import DndContext from './DndContext';
import { createDragDropManager } from '../../dnd-core';
function DndProvider({ backend, children }) {
let value = { dragDropManager: createDragDropManager(backend) };
return (
<DndContext.Provider value={value}>
{children}
</DndContext.Provider>
)
}
export default DndProvider;
src\dnd-core\index.js
export { default as createDragDropManager } from './createDragDropManager';
src\dnd-core\createDragDropManager.js
import { createStore } from 'redux';
import reducer from './reducers';
import DragDropManagerImpl from './classes/DragDropManagerImpl';
function createDragDropManager(backendFactory) {
//创建redux仓库
const store = createStore(reducer);
const manager = new DragDropManagerImpl(store);
const backend = backendFactory(manager);
manager.receiveBackend(backend);
return manager;
}
export default createDragDropManager;
src\dnd-core\classes\DragDropManagerImpl.js
class DragDropManagerImpl {
store;
backend;
constructor(store) {
this.store = store;
}
receiveBackend(backend) {
this.backend = backend;
}
getBackend() {
return this.backend;
}
}
export default DragDropManagerImpl;
src\dnd-core\reducers\index.js
function reducer(state = {}, action) {
return {};
}
export default reducer;
src\react-dnd-html5-backend\index.js
import HTML5BackendImpl from './HTML5BackendImpl';
export function HTML5Backend(manager) {
return new HTML5BackendImpl(manager);
}
src\react-dnd-html5-backend\HTML5BackendImpl.js
class HTML5BackendImpl {}
export default HTML5BackendImpl;
src\react-dnd\index.js
export * from './core'
+export * from './hooks';
src\react-dnd\hooks\index.js
export { default as useDrag } from './useDrag';
src\react-dnd\hooks\useDrag\index.js
import useDragSourceMonitor from './useDragSourceMonitor';
function useDrag(spec) {
const monitor = useDragSourceMonitor();
console.log(monitor);
return [{}, () => { }];
}
export default useDrag;
src\react-dnd\hooks\useDrag\useDragSourceMonitor.js
import { useMemo } from 'react';
import useDragDropManager from '../useDragDropManager';
import { DragSourceMonitorImpl } from '../../internals'
function useDragSourceMonitor() {
const manager = useDragDropManager();
return useMemo(() => new DragSourceMonitorImpl(manager), [manager]);
}
export default useDragSourceMonitor;
src\react-dnd\hooks\useDragDropManager.js
import { useContext } from 'react';
import { DndContext } from '../core';
function useDragDropManager() {
const { dragDropManager } = useContext(DndContext);
return dragDropManager;
}
export default useDragDropManager;
src\react-dnd\internals\index.js
export { default as DragSourceMonitorImpl } from './DragSourceMonitorImpl';
src\react-dnd\internals\DragSourceMonitorImpl.js
class DragSourceMonitorImpl {
internalMonitor
constructor(manager) {
this.internalMonitor = manager.getGlobalMonitor();
}
}
export default DragSourceMonitorImpl;
src\dnd-core\index.js
export { default as createDragDropManager } from './createDragDropManager';
export { default as DragDropManagerImpl } from './classes/DragDropManagerImpl';
+export { default as DragDropMonitorImpl } from './classes/DragDropMonitorImpl';
src\dnd-core\createDragDropManager.js
import { createStore } from 'redux';
import reducer from './reducers';
import DragDropManagerImpl from './classes/DragDropManagerImpl';
+import DragDropMonitorImpl from './classes/DragDropMonitorImpl';
function createDragDropManager(backendFactory) {
//创建redux仓库
const store = createStore(reducer);
+ const globalMonitor = new DragDropMonitorImpl(store);
+ const manager = new DragDropManagerImpl(store, globalMonitor);
const backend = backendFactory(manager);
manager.receiveBackend(backend);
return manager;
}
export default createDragDropManager;
src\dnd-core\classes\DragDropMonitorImpl.js
class DragDropMonitorImpl {
store
constructor(store) {
this.store = store
}
}
export default DragDropMonitorImpl;
src\dnd-core\classes\DragDropManagerImpl.js
class DragDropManagerImpl {
store;
backend;
+ globalMonitor;
+ constructor(store, globalMonitor) {
this.store = store;
+ this.globalMonitor = globalMonitor;
}
receiveBackend(backend) {
this.backend = backend;
}
getBackend() {
return this.backend;
}
+ getGlobalMonitor() {
+ return this.globalMonitor;
+ }
}
export default DragDropManagerImpl;
src\dnd-core\classes\HandlerRegistryImpl.js
class HandlerRegistryImpl {
store;//redux仓库
constructor(store) {
this.store = store;
}
}
export default HandlerRegistryImpl;
src\dnd-core\createDragDropManager.js
import { createStore } from 'redux';
import reducer from './reducers';
+import HandlerRegistryImpl from './classes/HandlerRegistryImpl'
import DragDropManagerImpl from './classes/DragDropManagerImpl';
import DragDropMonitorImpl from './classes/DragDropMonitorImpl';
function createDragDropManager(backendFactory) {
//创建redux仓库
const store = createStore(reducer);
+ const registry = new HandlerRegistryImpl(store);
+ const globalMonitor = new DragDropMonitorImpl(store, registry);
const manager = new DragDropManagerImpl(store, globalMonitor);
const backend = backendFactory(manager);
manager.receiveBackend(backend);
return manager;
}
export default createDragDropManager;
src\dnd-core\classes\DragDropMonitorImpl.js
class DragDropMonitorImpl {
store
+ registry
constructor(store, registry) {
this.store = store
+ this.registry = registry;
}
}
export default DragDropMonitorImpl;
src\react-dnd\hooks\useDrag\index.js
import useDragSourceMonitor from './useDragSourceMonitor';
+import useDragSourceConnector from './useDragSourceConnector';
function useDrag(spec) {
const monitor = useDragSourceMonitor();
+ const connector = useDragSourceConnector();
+ console.log(connector);
return [{}, () => { }];
}
export default useDrag;
src\react-dnd\hooks\useDrag\useDragSourceConnector.js
import { useMemo } from 'react';
import useDragDropManager from '../useDragDropManager';
import { SourceConnector } from '../../internals';
function useDragSourceConnector() {
const manager = useDragDropManager();
const connector = useMemo(() => new SourceConnector(manager.getBackend()), [manager]);
return connector;
}
export default useDragSourceConnector;
src\react-dnd\internals\index.js
export { default as DragSourceMonitorImpl } from './DragSourceMonitorImpl';
+export { default as SourceConnector } from './SourceConnector';
src\react-dnd\internals\SourceConnector.js
class SourceConnector {
backend
constructor(backend) {
this.backend = backend;
}
connect() {
console.log('连接React和DOM');
}
}
export default SourceConnector;
src\react-dnd\hooks\useDrag\index.js
import useDragSourceMonitor from './useDragSourceMonitor';
import useDragSourceConnector from './useDragSourceConnector';
+import useRegisteredDragSource from './useRegisteredDragSource';
function useDrag(spec) {
// const monitor = useDragSourceMonitor();
const connector = useDragSourceConnector();
+ useRegisteredDragSource(spec, monitor, connector);
return [{}, () => { }];
}
export default useDrag;
src\react-dnd\hooks\useDrag\useRegisteredDragSource.js
import useDragDropManager from '../useDragDropManager';
import useDragSource from './useDragSource';
import { useDragType } from './useDragType';
function useRegisteredDragSource(spec, monitor, connector) {
const manager = useDragDropManager();
const handler = useDragSource(spec, monitor, connector);
console.log('handler', handler);
const itemType = useDragType(spec);
console.log(itemType);
}
export default useRegisteredDragSource;
src\react-dnd\hooks\useDrag\useDragSource.js
/* eslint-disable react-hooks/exhaustive-deps */
import { useMemo, useEffect } from 'react';
import { DragSourceImpl } from './DragSourceImpl';
function useDragSource(spec, monitor, connector) {
const handler = useMemo(() => {
return new DragSourceImpl(spec, monitor, connector);
}, [monitor, connector]);
useEffect(() => {
handler.spec = spec;
}, [handler, spec]);
return handler;
}
export default useDragSource;
src\react-dnd\hooks\useDrag\DragSourceImpl.js
export class DragSourceImpl {
spec;
monitor;
connector;
constructor(spec, monitor, connector) {
this.spec = spec;
this.monitor = monitor;
this.connector = connector;
}
}
src\react-dnd\hooks\useDrag\useDragType.js
import { useMemo } from 'react';
export function useDragType(spec) {
return useMemo(() => spec.type, [spec]);
}
src\react-dnd\hooks\useDrag\useRegisteredDragSource.js
+import { useLayoutEffect } from 'react';
import useDragDropManager from '../useDragDropManager';
import useDragSource from './useDragSource';
import { useDragType } from './useDragType';
+import { registerSource } from '../../internals';
function useRegisteredDragSource(spec, monitor, connector) {
const manager = useDragDropManager();
const handler = useDragSource(spec, monitor, connector);
const itemType = useDragType(spec);
+ useLayoutEffect(function () {
+ const handlerId = registerSource(itemType, handler, manager);
+ monitor.receiveHandlerId(handlerId);
+ connector.receiveHandlerId(handlerId);
+ console.log('dragSources', manager.globalMonitor.registry.dragSources);
+ console.log('types', manager.globalMonitor.registry.types);
+ }, [connector, handler, itemType, manager, monitor]);
}
export default useRegisteredDragSource;
src\react-dnd\internals\DragSourceMonitorImpl.js
class DragSourceMonitorImpl {
internalMonitor
+ handlerId
constructor(manager) {
this.internalMonitor = manager.getGlobalMonitor();
}
+ receiveHandlerId(handlerId) {
+ this.handlerId = handlerId;
+ }
}
export default DragSourceMonitorImpl;
src\react-dnd\internals\SourceConnector.js
class SourceConnector {
backend
+ handlerId
constructor(backend) {
this.backend = backend;
}
connect() {
console.log('连接React和DOM');
}
+ receiveHandlerId(handlerId) {
+ this.handlerId = handlerId;
+ }
}
export default SourceConnector;
src\react-dnd\internals\registerSource.js
function registerSource(type, handler, manager) {
const registry = manager.getRegistry();
const handlerId = registry.addSource(type, handler);
return handlerId;
}
export default registerSource;
src\react-dnd\internals\index.js
export { default as DragSourceMonitorImpl } from './DragSourceMonitorImpl';
export { default as SourceConnector } from './SourceConnector';
+export { default as registerSource } from './registerSource';
src\dnd-core\classes\DragDropManagerImpl.js
class DragDropManagerImpl {
store;
backend;
globalMonitor;
constructor(store, globalMonitor) {
this.store = store;
this.globalMonitor = globalMonitor;
}
receiveBackend(backend) {
this.backend = backend;
}
getBackend() {
return this.backend;
}
getGlobalMonitor() {
return this.globalMonitor;
}
+ getRegistry() {
+ return this.globalMonitor.registry;
+ }
}
export default DragDropManagerImpl;
src\dnd-core\classes\HandlerRegistryImpl.js
+import { HandlerRole } from '../interfaces';
+import { getNextUniqueId } from '../utils';
+import { addSource } from '../actions/registry';
class HandlerRegistryImpl {
store;//redux仓库
+ types = new Map(); //不同handlerId的类型
+ dragSources = new Map();//拖放来源
constructor(store) {
this.store = store;
}
+ addSource(type, source) {
+ const handlerId = this.addHandler(HandlerRole.SOURCE, type, source);
+ this.store.dispatch(addSource(handlerId));
+ return handlerId;
+ }
+ addHandler(role, type, handler) {
+ const handlerId = getNextHandlerId(role);//获取下一个handleId
+ this.types.set(handlerId, type);
+ if (role === HandlerRole.SOURCE) {
+ this.dragSources.set(handlerId, handler);
+ }
+ return handlerId;
+ }
+ getSource(handlerId) {
+ return this.dragSources.get(handlerId);
+ }
+ getSourceType(handlerId) {
+ return this.types.get(handlerId);
+ }
}
+function getNextHandlerId(role) {
+ const id = getNextUniqueId().toString();
+ switch (role) {
+ case HandlerRole.SOURCE:
+ return `S${id}`;
+ case HandlerRole.TARGET:
+ return `T${id}`;
+ default:
+ throw new Error(`Unknown Handler Role: ${role}`);
+ }
+}
export default HandlerRegistryImpl;
src\dnd-core\interfaces.js
export var HandlerRole = {
"SOURCE": "SOURCE",
"TARGET": "TARGET"
}
src\dnd-core\utils\getNextUniqueId.js
let nextUniqueId = 0;
function getNextUniqueId() {
return nextUniqueId++;
}
export default getNextUniqueId;
src\dnd-core\utils\index.js
export { default as getNextUniqueId } from './getNextUniqueId';
src\dnd-core\actions\registry.js
export const ADD_SOURCE = 'dnd-core/ADD_SOURCE';
export function addSource(handlerId) {
return {
type: ADD_SOURCE,
payload: { handlerId }
};
}
src\react-dnd\hooks\useDrag\index.js
import useDragSourceMonitor from './useDragSourceMonitor';
import useDragSourceConnector from './useDragSourceConnector';
import useRegisteredDragSource from './useRegisteredDragSource';
+import useConnectDragSource from './useConnectDragSource';
function useDrag(spec) {
const monitor = useDragSourceMonitor();
const connector = useDragSourceConnector();
useRegisteredDragSource(spec, monitor, connector);
return [
{},
+ useConnectDragSource(connector)
];
}
export default useDrag;
src\react-dnd\hooks\useDrag\useConnectDragSource.js
import { useMemo } from 'react';
function useConnectDragSource(connector) {
return useMemo(() => connector.receiveDragSource, [connector]);
}
export default useConnectDragSource
src\react-dnd\internals\SourceConnector.js
class SourceConnector {
backend
handlerId //拖动源唯一标识
+ dragSourceRef //DOM节点
constructor(backend) {
this.backend = backend;
}
+ get dragSource() {
+ return this.dragSourceRef && this.dragSourceRef.current;
+ }
+ connect() {
+ if (!this.handlerId || !this.dragSource) {
+ return;
+ }
+ this.backend.connectDragSource(this.handlerId, this.dragSource);
+ }
//useRegisteredDragSource中useLayoutEffect赋值
receiveHandlerId(handlerId) {
this.handlerId = handlerId;
+ this.connect();
}
+ receiveDragSource = (dragSourceRef) => {
+ this.dragSourceRef = dragSourceRef;
+ }
}
export default SourceConnector;
src\react-dnd-html5-backend\HTML5BackendImpl.js
class HTML5BackendImpl {
+ dragStartSourceId
+ connectDragSource(handleId, node) {
+ node.setAttribute('draggable', 'true');
+ node.addEventListener('dragstart', (e) => this.handleDragStart(e, handleId));
+ }
+ handleDragStart(e, handleId) {
+ this.dragStartSourceId = handleId;
+ }
}
export default HTML5BackendImpl;
src\react-dnd\hooks\useDrag\index.js
import useDragSourceMonitor from './useDragSourceMonitor';
import useDragSourceConnector from './useDragSourceConnector';
import useRegisteredDragSource from './useRegisteredDragSource';
import useConnectDragSource from './useConnectDragSource';
+import { useCollectedProps } from '../useCollectedProps';
function useDrag(spec) {
const monitor = useDragSourceMonitor();
const connector = useDragSourceConnector();
useRegisteredDragSource(spec, monitor, connector);
return [
+ useCollectedProps(monitor, spec.collect),
useConnectDragSource(connector)
];
}
export default useDrag;
src\react-dnd\hooks\useCollectedProps.js
import { useLayoutEffect, useState, useCallback } from 'react';
import equal from 'fast-deep-equal';
export function useCollectedProps(monitor, collect) {
const [collected, setCollected] = useState(() => collect(monitor));
const updateCollected = useCallback(() => {
const nextValue = collect(monitor);
if (!equal(collected, nextValue)) {
setCollected(nextValue);
}
}, [collect, collected, monitor]);
useLayoutEffect(function () {
return monitor.subscribeToStateChange(updateCollected);
}, [monitor, updateCollected]);
return collected;
}
src\react-dnd\internals\DragSourceMonitorImpl.js
class DragSourceMonitorImpl {
internalMonitor
handlerId
constructor(manager) {
this.internalMonitor = manager.getGlobalMonitor();
}
receiveHandlerId(handlerId) {
this.handlerId = handlerId;
}
+ subscribeToStateChange(listener) {
+ return this.internalMonitor.subscribeToStateChange(listener);
+ }
+ isDragging() {
+ return this.internalMonitor.isDraggingSource(this.handlerId);
+ }
}
export default DragSourceMonitorImpl;
src\dnd-core\classes\DragDropMonitorImpl.js
class DragDropMonitorImpl {
store
registry
constructor(store, registry) {
this.store = store
this.registry = registry;
}
+ subscribeToStateChange(listener) {
+ return this.store.subscribe(listener);
+ }
+ getSourceId() {
+ return 'S0';
+ }
+ isDraggingSource(handlerId) {
+ return handlerId === this.getSourceId();
+ }
}
export default DragDropMonitorImpl;
src\dnd-core\reducers\dragOperation.js
import { BEGIN_DRAG, END_DRAG } from '../actions/dragDrop';
const initialState = {
itemType: null, //拖动元素的类型
item: null, //拖动的元素
sourceId: null //来源的ID
};
export function reduce(state = initialState, action) {
const { payload } = action;
switch (action.type) {
case BEGIN_DRAG:
return {
...state,
itemType: payload.itemType,
item: payload.item,
sourceId: payload.sourceId
};
case END_DRAG:
return {
...state,
itemType: null,
item: null,
sourceId: null
};
default:
return state;
}
}
src\dnd-core\reducers\index.js
+import { reduce as dragOperation, } from './dragOperation';
+import { combineReducers } from 'redux'
+const reducers = {
+ dragOperation
+};
+export default combineReducers(reducers);
src\dnd-core\actions\dragDrop\index.js
import { createBeginDrag } from './beginDrag';
import { createEndDrag } from './endDrag';
export * from './types';
export function createDragDropActions(manager) {
return {
beginDrag: createBeginDrag(manager),
endDrag: createEndDrag(manager),
};
}
src\dnd-core\actions\dragDrop\types.js
export const BEGIN_DRAG = 'dnd-core/BEGIN_DRAG';
export const END_DRAG = 'dnd-core/END_DRAG';
src\dnd-core\actions\dragDrop\beginDrag.js
import { BEGIN_DRAG } from './types';
export function createBeginDrag(manager) {
return function beginDrag(sourceId) {
const monitor = manager.getGlobalMonitor();
const registry = manager.getRegistry();
const source = registry.getSource(sourceId);
const item = source.beginDrag(monitor, sourceId);
const itemType = registry.getSourceType(sourceId);
return {
type: BEGIN_DRAG,
payload: {
itemType,
item,
sourceId
},
};
}
}
src\react-dnd\hooks\useDrag\DragSourceImpl.js
export class DragSourceImpl {
spec;
monitor;
connector;
constructor(spec, monitor, connector) {
this.spec = spec;
this.monitor = monitor;
this.connector = connector;
}
+ beginDrag() {
+ const spec = this.spec;
+ const monitor = this.monitor;
+ return spec.item(monitor);
+ }
}
src\dnd-core\actions\dragDrop\endDrag.js
import { END_DRAG } from './types';
export function createEndDrag(manager) {
return function endDrag() {
return { type: END_DRAG };
};
}
src\dnd-core\classes\DragDropMonitorImpl.js
class DragDropMonitorImpl {
store
registry
constructor(store, registry) {
this.store = store
this.registry = registry;
}
subscribeToStateChange(listener) {
return this.store.subscribe(listener);
}
+ getSourceId() {
+ return this.store.getState().dragOperation.sourceId;
+ }
isDraggingSource(sourceId) {
return sourceId === this.getSourceId();
}
}
export default DragDropMonitorImpl;
src\dnd-core\classes\DragDropManagerImpl.js
+import { createDragDropActions } from '../actions/dragDrop';
+import { bindActionCreators } from 'redux';
class DragDropManagerImpl {
store;
backend;
globalMonitor;
constructor(store, globalMonitor) {
this.store = store;
this.globalMonitor = globalMonitor;
}
receiveBackend(backend) {
this.backend = backend;
+ this.backend.setup();
}
getBackend() {
return this.backend;
}
getGlobalMonitor() {
return this.globalMonitor;
}
getRegistry() {
return this.globalMonitor.registry;
}
+ getActions() {
+ const actions = createDragDropActions(this);
+ return bindActionCreators(actions, this.store.dispatch);
+ }
}
export default DragDropManagerImpl;
src\react-dnd-html5-backend\HTML5BackendImpl.js
class HTML5BackendImpl {
dragStartSourceId
+ actions
+ constructor(manager) {
+ this.actions = manager.getActions();
+ }
+ setup() {
+ this.addEventListeners(window);
+ }
+ addEventListeners(target) {
+ target.addEventListener('dragstart', this.handleTopDragStart);
+ target.addEventListener('dragend', this.handleTopDragEndCapture, true);
+ }
+ handleTopDragStart = (e) => {
+ this.actions.beginDrag(this.dragStartSourceId);
+ }
+ handleTopDragEndCapture = () => {
+ this.actions.endDrag();
+ };
connectDragSource(handleId, node) {
node.setAttribute('draggable', 'true');
node.addEventListener('dragstart', (e) => this.handleDragStart(e, handleId));
}
handleDragStart(e, handleId) {
this.dragStartSourceId = handleId;
}
}
export default HTML5BackendImpl;
src\react-dnd\hooks\useDrop\index.js
import useDropTargetMonitor from './useDropTargetMonitor';
import useDropTargetConnector from './useDropTargetConnector';
import useRegisteredDropTarget from './useRegisteredDropTarget';
import useConnectDropTarget from './useConnectDropTarget';
import { useCollectedProps } from '../useCollectedProps';
function useDrag(spec) {
const monitor = useDropTargetMonitor();
const connector = useDropTargetConnector();
useRegisteredDropTarget(spec, monitor, connector);
return [
useCollectedProps(monitor, spec.collect),
useConnectDropTarget(connector)
];
}
export default useDrag;
src\react-dnd\hooks\useDrop\useDropTargetMonitor.js
import { useMemo } from 'react';
import useDragDropManager from '../useDragDropManager';
import { DropTargetMonitorImpl } from '../../internals'
function useDropTargetMonitor() {
const manager = useDragDropManager();
return useMemo(() => new DropTargetMonitorImpl(manager), [manager]);
}
export default useDropTargetMonitor;
src\react-dnd\internals\DropTargetMonitorImpl.js
class DragSourceMonitorImpl {
internalMonitor
handlerId
constructor(manager) {
this.internalMonitor = manager.getGlobalMonitor();
}
receiveHandlerId(handlerId) {
this.handlerId = handlerId;
}
subscribeToStateChange(listener) {
return this.internalMonitor.subscribeToStateChange(listener);
}
isDragging() {
return this.internalMonitor.isDraggingSource(this.handlerId);
}
getItemType() {
return this.internalMonitor.getItemType();
}
getItem() {
return this.internalMonitor.getItem();
}
getClientOffset() {
return this.internalMonitor.getClientOffset();
}
}
export default DragSourceMonitorImpl;
src\react-dnd\hooks\useDrop\useDropTargetConnector.js
import { useMemo } from 'react';
import useDragDropManager from '../useDragDropManager';
import { TargetConnector } from '../../internals';
function useDropTargetConnector() {
const manager = useDragDropManager();
const connector = useMemo(() => new TargetConnector(manager.getBackend()), [manager]);
return connector;
}
export default useDropTargetConnector;
src\react-dnd\internals\TargetConnector.js
class TargetConnector {
backend
handlerId //拖动源唯一标识
dropTargetRef //DOM节点
constructor(backend) {
this.backend = backend;
}
get dropTarget() {
return this.dropTargetRef && this.dropTargetRef.current;
}
connect() {
if (!this.handlerId || !this.dropTarget) {
return;
}
this.backend.connectDropTarget(this.handlerId, this.dropTarget);
}
//useRegisteredDragSource中useLayoutEffect赋值
receiveHandlerId(handlerId) {
this.handlerId = handlerId;
this.connect();
}
receiveDropTarget = (dropTargetRef) => {
this.dropTargetRef = dropTargetRef;
}
}
export default TargetConnector;
src\react-dnd\hooks\useDrop\useRegisteredDropTarget.js
import { useLayoutEffect } from 'react';
import useDragDropManager from '../useDragDropManager';
import useDropTarget from './useDropTarget';
import { registerTarget } from '../../internals';
function useRegisteredDropTarget(spec, monitor, connector) {
const manager = useDragDropManager();
const handler = useDropTarget(spec, monitor, connector);
const type = spec.type;
useLayoutEffect(function () {
const handlerId = registerTarget(type, handler, manager);
monitor.receiveHandlerId(handlerId);
connector.receiveHandlerId(handlerId);
}, [connector, handler, type, manager, monitor]);
}
export default useRegisteredDropTarget;
src\react-dnd\hooks\useDrop\useDropTarget.js
/* eslint-disable react-hooks/exhaustive-deps */
import { useMemo, useEffect } from 'react';
import { DropTargetImpl } from './DropTargetImpl';
function useDropTarget(spec, monitor, connector) {
const handler = useMemo(() => {
return new DropTargetImpl(spec, monitor, connector);
}, [monitor, connector]);
useEffect(() => {
handler.spec = spec;
}, [handler, spec]);
return handler;
}
export default useDropTarget;
src\react-dnd\hooks\useDrop\DropTargetImpl.js
export class DropTargetImpl {
spec;
monitor;
connector;
constructor(spec, monitor, connector) {
this.spec = spec;
this.monitor = monitor;
this.connector = connector;
}
hover() {
const spec = this.spec;
const monitor = this.monitor;
if (spec.hover) {
spec.hover(monitor.getItem(), monitor);
}
}
}
src\react-dnd\internals\registerTarget.js
function registerTarget(type, target, manager) {
const registry = manager.getRegistry();
const targetId = registry.addTarget(type, target);
return targetId;
}
export default registerTarget;
src\react-dnd\hooks\useDrop\useConnectDropTarget.js
import { useMemo } from 'react';
function useConnectDropTarget(connector) {
return useMemo(() => connector.receiveDropTarget, [connector]);
}
export default useConnectDropTarget
src\react-dnd\hooks\index.js
export { default as useDrag } from './useDrag';
+export { default as useDrop } from './useDrop';
src\react-dnd\internals\index.js
export { default as DragSourceMonitorImpl } from './DragSourceMonitorImpl';
+export { default as DropTargetMonitorImpl } from './DropTargetMonitorImpl';
export { default as SourceConnector } from './SourceConnector';
+export { default as TargetConnector } from './TargetConnector';
export { default as registerSource } from './registerSource';
+export { default as registerTarget } from './registerTarget';
src\react-dnd-html5-backend\HTML5BackendImpl.js
+import { getEventClientOffset } from './OffsetUtils';
class HTML5BackendImpl {
dragStartSourceId
actions
+ dragOverTargetId
constructor(manager) {
this.actions = manager.getActions();
}
setup() {
this.addEventListeners(window);
}
addEventListeners(target) {
target.addEventListener('dragstart', this.handleTopDragStart);
+ target.addEventListener('dragover', this.handleTopDragOver);
target.addEventListener('dragend', this.handleTopDragEndCapture, true);
}
handleTopDragStart = (e) => {
this.actions.beginDrag(this.dragStartSourceId);
}
handleTopDragEndCapture = () => {
this.actions.endDrag();
};
connectDragSource(handleId, node) {
node.setAttribute('draggable', 'true');
node.addEventListener('dragstart', (e) => this.handleDragStart(e, handleId));
}
handleDragStart(e, handleId) {
this.dragStartSourceId = handleId;
}
+ handleTopDragOver = (e) => {
+ this.actions.hover(this.dragOverTargetId, {
+ clientOffset: getEventClientOffset(e)
+ });
+ };
+ handleDragOver(e, targetId) {
+ this.dragOverTargetId = targetId;
+ }
+ connectDropTarget(targetId, node) {
+ console.log('connectDropTarget', targetId);
+ node.addEventListener('dragover', (e) => this.handleDragOver(e, targetId));
+ }
}
export default HTML5BackendImpl;
src\react-dnd-html5-backend\OffsetUtils.js
/**
* 获取事件在浏览器可视区域的坐标
* @param {*} e
* @returns
*/
export function getEventClientOffset(e) {
return {
x: e.clientX,//鼠标相对于浏览器窗口可视区域的X
y: e.clientY//鼠标相对于浏览器窗口可视区域的Y坐标
};
}
src\dnd-core\classes\DragDropMonitorImpl.js
class DragDropMonitorImpl {
store
registry
constructor(store, registry) {
this.store = store
this.registry = registry;
}
subscribeToStateChange(listener) {
return this.store.subscribe(listener);
}
getSourceId() {
return this.store.getState().dragOperation.sourceId;
}
isDraggingSource(sourceId) {
return sourceId === this.getSourceId();
}
+ getItem() {
+ return this.store.getState().dragOperation.item;
+ }
+ getItemType() {
+ return this.store.getState().dragOperation.itemType;
+ }
+ getClientOffset() {
+ return this.store.getState().dragOffset.clientOffset;
+ }
}
export default DragDropMonitorImpl;
src\dnd-core\classes\HandlerRegistryImpl.js
import { HandlerRole } from '../interfaces';
import { getNextUniqueId } from '../utils';
+import { addSource, addTarget } from '../actions/registry';
class HandlerRegistryImpl {
store;//redux仓库
types = new Map(); //不同handlerId的类型
dragSources = new Map();//拖放来源
+ dropTargets = new Map();//放置目标
constructor(store) {
this.store = store;
}
addSource(type, source) {
const handlerId = this.addHandler(HandlerRole.SOURCE, type, source);
this.store.dispatch(addSource(handlerId));
return handlerId;
}
addHandler(role, type, handler) {
const handlerId = getNextHandlerId(role);//获取下一个handleId
this.types.set(handlerId, type);
if (role === HandlerRole.SOURCE) {
this.dragSources.set(handlerId, handler);
+ } else if (role === HandlerRole.TARGET) {
+ this.dropTargets.set(handlerId, handler);
+ }
return handlerId;
}
getSource(handlerId) {
return this.dragSources.get(handlerId);
}
getSourceType(handlerId) {
return this.types.get(handlerId);
}
+ addTarget(type, target) {
+ const targetId = this.addHandler(HandlerRole.TARGET, type, target);
+ this.store.dispatch(addTarget(targetId));
+ return targetId;
+ }
+ getTarget(targetId) {
+ return this.dropTargets.get(targetId);
+ }
+ getTargetType(targetId) {
+ return this.types.get(targetId);
+ }
}
function getNextHandlerId(role) {
const id = getNextUniqueId().toString();
switch (role) {
case HandlerRole.SOURCE:
return `S${id}`;
case HandlerRole.TARGET:
return `T${id}`;
default:
throw new Error(`Unknown Handler Role: ${role}`);
}
}
export default HandlerRegistryImpl;
src\dnd-core\actions\registry.js
export const ADD_SOURCE = 'dnd-core/ADD_SOURCE';
+export const ADD_TARGET = 'dnd-core/ADD_TARGET';
export function addSource(handlerId) {
return {
type: ADD_SOURCE,
payload: { handlerId }
};
}
+export function addTarget(targetId) {
+ return {
+ type: ADD_TARGET,
+ payload: {
+ targetId
+ },
+ };
+}
src\dnd-core\reducers\index.js
import { reduce as dragOperation, } from './dragOperation';
+import { reduce as dragOffset } from './dragOffset';
import { combineReducers } from 'redux'
const reducers = {
dragOperation,
+ dragOffset
};
export default combineReducers(reducers);
src\dnd-core\reducers\dragOffset.js
import { BEGIN_DRAG, HOVER, END_DRAG } from '../actions/dragDrop';
const initialState = {
clientOffset: {},
};
export function reduce(state = initialState, action) {
const { payload } = action;
switch (action.type) {
case BEGIN_DRAG:
return {
clientOffset: payload.clientOffset,
};
case HOVER:
return {
clientOffset: payload.clientOffset
};
case END_DRAG:
return initialState;
default:
return state;
}
}
src\dnd-core\actions\dragDrop\index.js
import { createBeginDrag } from './beginDrag';
import { createEndDrag } from './endDrag';
+import { createHover } from './hover';
export * from './types';
export function createDragDropActions(manager) {
return {
beginDrag: createBeginDrag(manager),
endDrag: createEndDrag(manager),
+ hover: createHover(manager)
};
}
src\dnd-core\actions\dragDrop\hover.js
import { HOVER } from './types';
export function createHover(manager) {
return function hover(targetId, { clientOffset } = {}) {
const registry = manager.getRegistry();//HandlerRegistryImpl
const target = registry.getTarget(targetId);//DropTargetImpl
queueMicrotask(target.hover.bind(target));
return {
type: HOVER,
payload: {
targetId,
clientOffset
},
};
};
}
src\dnd-core\actions\dragDrop\types.js
export const BEGIN_DRAG = 'dnd-core/BEGIN_DRAG';
export const END_DRAG = 'dnd-core/END_DRAG';
+export const HOVER = 'dnd-core/HOVER';