- 前端
- UI组件
【免费下载链接】react-dnd
Drag and Drop for React
useDragDropManager 是 React DnD 提供给开发者的一把"钥匙",它让你在函数组件中直接拿到整个拖拽系统的核心单例——DragDropManager,从而访问状态、监视器(monitor)、后端(backend)与注册表(registry)等全部内部能力。本文以 useDragDropManager.md 为骨架,结合 dnd-core 与 react-dnd 源码,讲解它的用法、底层实现、在内部 Hook 中的应用场景,以及自定义监视器的实战写法。
useDragDropManager 是什么
在 React DnD 中,DndProvider负责把后端(backend)注入应用,而 DragDropManager 则是由 React DnD 创建的单例对象,它集中管理拖拽系统的全部核心资源。官方文档 useDragDropManager 对其定位是:
The DragDropManager instance is a singleton created by React DnD that contains access to state, monitors, the backend, etc..
也就是说,通过useDragDropManager()这个 Hook,你可以在任意函数组件中获得对整个 DnD 系统的访问句柄——这正是它与useDrag、useDrop等"自动化" Hook 最大的区别:它不是帮你做某件具体的事,而是把系统内部的能力直接交到你手上。
基本用法
useDragDropManager的用法极其简单:从react-dnd导入,在组件内调用一次,即可拿到DragDropManager类型的实例。官方文档给出了最小示例:
import { useDragDropManager } from 'react-dnd' function Example() { // The manager provides access to all of React DnD's internals const dragDropManager = useDragDropManager() return <div>Example</div> }注意一个前提:必须在DndProvider内部使用。因为该 Hook 的取值来源是 React Context,而DndProvider正是负责把 manager 注入 Context 的组件。如果在没有 Provider 包裹的地方调用,会抛出Expected drag drop context异常(详见下文源码分析)。
底层实现:从 Context 取值的单行 Hook
useDragDropManager的实现非常精简,完整源码位于 useDragDropManager.ts:
import { invariant } from '@react-dnd/invariant' import type { DragDropManager } from 'dnd-core' import { useContext } from 'react' import { DndContext } from '../core/index.js' /** * A hook to retrieve the DragDropManager from Context */ export function useDragDropManager(): DragDropManager { const { dragDropManager } = useContext(DndContext) invariant(dragDropManager != null, 'Expected drag drop context') return dragDropManager as DragDropManager }拆解来看,它由三步构成:
- 读取 Context:从
DndContext中解构出dragDropManager字段。这个 Context 定义在 DndContext.ts,其类型DndContextType只有一个可选字段:
export interface DndContextType { dragDropManager: DragDropManager | undefined } export const DndContext = createContext<DndContextType>({ dragDropManager: undefined, })守卫校验:
invariant(dragDropManager != null, 'Expected drag drop context')是来自@react-dnd/invariant的运行时断言。当你在DndProvider之外调用该 Hook 时,Context 的默认值是{ dragDropManager: undefined },断言立即失败并抛出Expected drag drop context,这比返回undefined后让你在深层代码里排查空引用要友好得多。类型收窄:通过断言后,将
dragDropManager收窄为DragDropManager类型返回。
从源码结构看,这个 Hook 只有读取、校验、返回三步,没有任何订阅或副作用逻辑,因此它是极其轻量且幂等的——在任意组件中多次调用也不会产生额外开销。
manager 从哪里来:DndProvider 的注入机制
要理解useDragDropManager拿到的对象,就要看它的提供者DndProvider。DndProvider.tsx 支持两种模式:
export type DndProviderProps<BackendContext, BackendOptions> = | { children?: ReactNode manager: DragDropManager } | { backend: BackendFactory children?: ReactNode context?: BackendContext options?: BackendOptions debugMode?: boolean }- 传入
manager:你自行创建 manager 并注入({ dragDropManager: props.manager }),适合需要复用 manager 的场景; - 传入
backend:DndProvider内部调用createSingletonDndContext创建 manager。关键点在于它会将 manager 缓存到全局对象(window/global)的Symbol.for('__REACT_DND_CONTEXT_INSTANCE__')上,保证全局只有一个单例;同时用refCount引用计数,在最后一个使用全局实例的DndProvider卸载时清理缓存,避免内存泄漏(源码注释原文:clean it up to avoid memory leaks)。
创建 manager 的实际动作发生在 dnd-core 的 createDragDropManager.ts:
export function createDragDropManager( backendFactory: BackendFactory, globalContext: unknown = undefined, backendOptions: unknown = {}, debugMode = false, ): DragDropManager { const store = makeStoreInstance(debugMode) const monitor = new DragDropMonitorImpl(store, new HandlerRegistryImpl(store)) const manager = new DragDropManagerImpl(store, monitor) const backend = backendFactory(manager, globalContext, backendOptions) manager.receiveBackend(backend) return manager }可见 manager 内部是Redux 架构:一个store(dnd-core 自己集成了 redux)、一个DragDropMonitorImpl监视器、一个HandlerRegistryImpl注册表,以及由backendFactory创建并注入的后端。debugMode为true且存在 Redux DevTools 扩展时,store 会被接入名为dnd-core的 DevTools 实例,便于调试。
manager 能做什么:DragDropManager 接口全览
通过useDragDropManager()拿到的实例类型定义在 interfaces.ts,它暴露了五个核心入口:
export interface DragDropManager { getMonitor(): DragDropMonitor getBackend(): Backend getRegistry(): HandlerRegistry getActions(): DragDropActions dispatch(action: any): void }| 方法 | 返回类型 | 作用 |
|---|---|---|
getMonitor() | DragDropMonitor | 只读监视器,查询拖拽状态、偏移量、drop 结果等 |
getBackend() | Backend | 后端实例(如 HTML5Backend、TouchBackend),可调用setup()/teardown()及连接方法 |
getRegistry() | HandlerRegistry | 注册表,管理 drag source / drop target 的 handler 注册与移除 |
getActions() | DragDropActions | 派发拖拽动作:beginDrag、publishDragSource、hover、drop、endDrag |
dispatch(action) | void | 向内部 Redux store 派发任意 action |
其中DragDropMonitor提供了丰富的查询 API,常用的包括:
subscribeToStateChange(listener)/subscribeToOffsetChange(listener):订阅状态与偏移量变化;isDragging()、isOverTarget(targetId):拖拽进行中 / 指针悬停判断;getItem()、getItemType():当前拖拽项及其类型;getClientOffset()、getInitialClientOffset()、getDifferenceFromInitialOffset():指针坐标与偏移差值;getDropResult()、didDrop():drop 结果与是否被处理。
这些 API 是你编写自定义监视器、自定义拖拽层时的核心工具。
源码中的真实应用:其他内置 Hook 都在用它
useDragDropManager并非孤立存在——React DnD 内部几乎所有的 Hook 都建立在它之上。在 hooks/index.ts 中,它与其他 Hook 一并导出:
export * from './types.js' export * from './useDrag/index.js' export * from './useDragDropManager.js' export * from './useDragLayer.js' export * from './useDrop/index.js'useDragLayer:监视器模式的典范
useDragLayer.ts 展示了标准的"拿 manager → 取 monitor → 订阅更新"模式:
export function useDragLayer<CollectedProps, DragObject = any>( collect: (monitor: DragLayerMonitor<DragObject>) => CollectedProps, ): CollectedProps { const dragDropManager = useDragDropManager() const monitor = dragDropManager.getMonitor() const [collected, updateCollected] = useCollector(monitor, collect) useEffect(() => monitor.subscribeToOffsetChange(updateCollected)) useEffect(() => monitor.subscribeToStateChange(updateCollected)) return collected }useDrag:注册拖拽源
useRegisteredDragSource.ts 则在挂载阶段通过 manager 完成 handler 注册:
const manager = useDragDropManager() // ... useIsomorphicLayoutEffect(function registerDragSource() { if (itemType != null) { const [handlerId, unregister] = registerSource(itemType, handler, manager) monitor.receiveHandlerId(handlerId) connector.receiveHandlerId(handlerId) return unregister } return }, [manager, monitor, connector, handler, itemType])同样,useDrop一侧的useDropTargetMonitor、useDropTargetConnector、useRegisteredDropTarget,以及useDrag一侧的useDragSourceMonitor、useDragSourceConnector都通过useDragDropManager()获取 manager。可以说,这个 Hook 是整个 react-dnd hooks 体系的基石。
实战:用 useDragDropManager 构建自定义拖拽层
useDragDropManager最典型的实战价值,是让你绕开useDrag/useDrop的封装,直接基于 manager 的 monitor 与 actions 编写自定义逻辑。下面是一个结合官方 custom-drag-layer 思路的最小自定义拖拽层示例:
import { useEffect, useState } from 'react' import { useDragDropManager } from 'react-dnd' function MyCustomDragLayer() { const dragDropManager = useDragDropManager() const monitor = dragDropManager.getMonitor() const [item, setItem] = useState(null) const [offset, setOffset] = useState({ x: 0, y: 0 }) useEffect(() => { // 拖拽状态变化时同步数据 const unsubState = monitor.subscribeToStateChange(() => { setItem(monitor.getItem()) }) // 指针偏移变化时更新坐标 const unsubOffset = monitor.subscribeToOffsetChange(() => { setOffset(monitor.getSourceClientOffset() ?? { x: 0, y: 0 }) }) return () => { unsubState() unsubOffset() } }, [monitor]) if (!monitor.isDragging()) return null return ( <div style={{ position: 'fixed', left: offset.x, top: offset.y, pointerEvents: 'none' }}> {item && <div>{item.id} 的拖拽预览</div>} </div> ) }关键点说明:
- 订阅生命周期:
subscribeToStateChange与subscribeToOffsetChange都会返回退订函数,务必在组件卸载时调用以避免泄漏; - 空值兜底:
getSourceClientOffset()在无拖拽时返回null,使用??提供默认值更安全; - 性能考虑:坐标变化会高频触发,建议配合
useCollector(React DnD 内部工具,见 useCollector.ts)做收集与 memo 化,避免不必要的重渲染。
注意事项与常见问题
- 必须在 DndProvider 内部调用:脱离 Provider 会抛出
Expected drag drop context。请确保组件树被<DndProvider backend={HTML5Backend}>包裹(参考 DndProvider 文档)。 - 拿到的是共享单例:通过 backend 模式创建时,manager 缓存在全局,所有组件拿到的是同一个实例。读取是安全的,但直接
dispatch自定义 action 需要你对 dnd-core 内部状态有充分理解,避免破坏拖拽状态机。 - 返回类型为
DragDropManager:这是 dnd-core 的抽象接口。如需类型体操,可配合dnd-core的DragDropManager、DragDropMonitor等类型进行标注。 - TS 环境下的导入:源码中所有内部导入均带
.js后缀(ESM 约定),但你在应用代码里使用import { useDragDropManager } from 'react-dnd'即可,包发布时已处理好模块格式。
延伸阅读
- Hook 源码:useDragDropManager.ts
- Context 定义:DndContext.ts
- Provider 注入机制:DndProvider.tsx
- 接口定义(DragDropManager / DragDropMonitor / Backend 等):interfaces.ts
- manager 创建过程:createDragDropManager.ts
- 内部消费示例:useDragLayer.ts、useRegisteredDragSource.ts
- 官方文档:useDragDropManager 文档、DndProvider 文档
- 前端
- UI组件
【免费下载链接】react-dnd
Drag and Drop for React
相关推荐
Deep-Live-Cam 实时换脸部署教程:没有显卡,一张照片也能快速跑通
Deep Live Cam 实时换脸部署教程:没有显卡,一张照片也能快速跑通 Deep Live Cam 是开源实时换脸工具:一张正脸照片,就能把视频或摄像头画
人工智能AI 应用计算机视觉媒体生成react-dnd DndProvider 完全指南:后端注入、配置选项与 DragDropManager 单例机制
react dnd DndProvider 完全指南:后端注入、配置选项与 DragDropManager 单例机制 DndProvider 是 react d
前端UI组件海洋航行器水动力学到运动控制仿真的完整实战路径:6自由度建模、GNC与仿真一站打通
海洋航行器水动力学到运动控制仿真的完整实战路径:6自由度建模、GNC与仿真一站打通 给无人艇写的路径跟踪控制器,在自己的仿真里跑出来是一条漂亮的直线;加上阻尼、
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考