安装
# yarntyarn add react-dnd react-dnd-html5-backend immutability-helper
immutability-helper
是一个深度复制的代码
卸载
tyarn remove react-dnd react-dnd-html5-backend immutability-helper
常见问题:如果发现一直显示不了页面,那么要删除.umi
临时文件夹,如果把不保险,把node_modules
也删除了。 重新运行环境。
React DnD 与大多数拖放库不同,如果您以前从未使用过它,它可能会令人生畏。但是,一旦您了解了其设计核心的一些概念,它就会开始变得有意义。
React DnD 使用数据而不是视图作为事实的来源。当您在屏幕上拖动某物时,我们并不是说正在拖动组件或 DOM 节点。相反,我们说某种类型的项目正在被拖动。
例如下面每个图片的格子或者棋盘上的格子,都可以称作 Item。
当您拖动卡片时,项目可能看起来像{ cardId: 42 }
。在国际象棋游戏中,当您拿起棋子时,该物品可能看起来像{ fromCell: 'C5', piece: 'queen' }
。将拖动的数据描述为普通对象可以帮助您保持组件解耦并且彼此不知道。
每个 Item 都会属于一个类型。类型的作用是什么呢?
相同 type 的 Item 可以被统一的逻辑进行处理。
监视器 Monitors 允许您更新组件的道具以响应拖放状态的变化。
对于需要跟踪拖放状态的每个组件,您可以定义一个收集函数【collect】,从监视器检索它的相关位。React DnD 然后负责及时调用您的收集函数并将其返回值合并到组件的 props 中。
下面代码,使用了 Hooks,其中定义了一个collect
收集函数,并将监视器monitor
中的内容返回到最上层[{isDragging}, drag]
,然后将这些内容,传递给组件。
const [{ isDragging }, drag] = useDrag(() => ({type: ItemTypes.KNIGHT,collect: (monitor) => ({isDragging: !!monitor.isDragging(),}),}));
React-dnd 中又两种方式:Connect 与 useHooks,推荐是用 Hooks 作为与现有组件的挂钩。
例如下面的代码,使用ref
就与drag
进行挂钩了。
return (<divref={drag}>♘</div>,)
当然在程序中,会经常看到这样的代码,思考一下又什么作用?:一些组件可以是拖动组件也可以是容器组件。
drag(drop(ref));
【Drag Sources and Drop Targets】拖动源与放置目标。
Backends,React DnD 使用HTML5 拖放 API。这是一个合理的默认设置,因为它会截取拖动的 DOM 节点并将其用作开箱即用的“拖动预览”。当光标移动时,您不必进行任何绘图,这很方便。此 API 也是处理文件放置事件的唯一方法。
不幸的是,HTML5 拖放 API 也有一些缺点。它不适用于触摸屏,并且它在 IE 上提供的定制机会比其他浏览器少。
这就是为什么在 React DnD中以可插拔方式实现 HTML5 拖放支持的原因。你不必使用它。您可以根据触摸事件、鼠标事件或其他完全不同的事件编写不同的实现。这种可插拔的实现在 React DnD 中被称为后端。
<DndProvider backend={HTML5Backend}>.....</DndProvider>
为了是 Hooks 的天下,忘记高阶函数与装饰器吧。
如果您不熟悉 React hooks,请参阅 React 博客文章Introducing Hooks。
完成静态部分后,再添加拖放交互。
主要有以下步骤:
Knight
,我们孤独的骑士片;Square
, 棋盘上的一个正方形;Board
,整板有 64 个方格。将棋盘Board
中用DndProvider
包裹。
import React from 'react';import { DndProvider } from 'react-dnd';import { HTML5Backend } from 'react-dnd-html5-backend';function Board() {/* ... */return <DndProvider backend={HTML5Backend}>...</DndProvider>;}
可以单独定义一个文件,叫做ItemTypes.ts
export const ItemTypes = {KNIGHT: 'knight',};
使用了useDrag
,并用ref={drag}
将骑士的div
关联了起来。
import React from 'react'import { ItemTypes } from './Constants'import { useDrag } from 'react-dnd'function Knight() {const [{isDragging}, drag] = useDrag(() => ({type: ItemTypes.KNIGHT,collect: monitor => ({isDragging: !!monitor.isDragging(),}),}))return (<divref={drag}style={{opacity: isDragging ? 0.5 : 1,fontSize: 25,fontWeight: 'bold',cursor: 'move',}}>♘</div>,)}export default Knight
实现完毕的效果如下:
现在Knight
是一个拖动源,但还没有放置目标来处理放置。我们现在要制作Square
一个放置目标。
import React from 'react'import Square from './Square'import { canMoveKnight, moveKnight } from './Game'import { ItemTypes } from './Constants'import { useDrop } from 'react-dnd'function BoardSquare({ x, y, children }) {const black = (x + y) % 2 === 1const [{ isOver }, drop] = useDrop(() => ({accept: ItemTypes.KNIGHT,drop: () => moveKnight(x, y),collect: monitor => ({isOver: !!monitor.isOver(),}),}), [x, y])return (<divref={drop}style={{position: 'relative',width: '100%',height: '100%',}}><Square black={black}>{children}</Square>{isOver && (<divstyle={{position: 'absolute',top: 0,left: 0,height: '100%',width: '100%',zIndex: 1,opacity: 0.5,backgroundColor: 'yellow',}}/>)}</div>,)}export default BoardSquare
实现的效果如下:
在useDrag
中添加preview
预览效果
const [{ isDragging }, drag, preview] = useDrag(() => ({type: ItemTypes.KNIGHT,collect: (monitor) => ({isDragging: !!monitor.isDragging(),}),}));
如果将preview
与组件关联呢?
<DragPreviewImage connect={preview} src={knightImage} />
在react-dnd
中定义了一个DragPreviewImage
组件
import { CSSProperties, FC } from 'react';import { DragPreviewImage, useDrag } from 'react-dnd';import { ItemTypes } from './ItemTypes';import { knightImage } from './knightImage';const knightStyle: CSSProperties = {fontSize: 40,fontWeight: 'bold',cursor: 'move',};export const Knight: FC = () => {const [{ isDragging }, drag, preview] = useDrag(() => ({type: ItemTypes.KNIGHT,collect: (monitor) => ({isDragging: !!monitor.isDragging(),}),}),[],);return (<><DragPreviewImage connect={preview} src={knightImage} /><divref={drag}style={{...knightStyle,opacity: isDragging ? 0.5 : 1,}}>♘</div></>);};
也可以使用自定义的一个 div,例如下面的例子
mport { useDrag } from 'react-dnd'function DraggableComponent(props) {const [collected, drag, dragPreview] = useDrag(() => ({type,item: { id }}))return collected.isDragging ? (<div ref={dragPreview} />) : (<div ref={drag} {...collected}>...</div>)}
分类 | 名称 | 说明 | 备注 |
---|---|---|---|
通用组件 | DndProvider | Dnd 提供者 | |
DragPreviewImage | 拖动预览图像 | ||
钩子 API | useDrag | 使用拖拽钩子 | |
useDrop | 使用放置钩子 | ||
useDragLayer | 使用拖动层钩子 | ||
useDragDropManager | 使用拖放管理器钩子 | (开发/测试挂钩) | |
监控状态 | DragSourceMonitor | 拖动源监视器 | |
DropTargetMonitor | 放置目标监视器 | ||
DragLayerMonitor | 拖动层监视器 |
> yarn install> yarn build> yarn start
isDragging
实现如何使拖动源显示为已拖动,即使启动拖动的组件已收到新的道具。DragLayer
可用于在应用程序顶部实现一个固定层,您可以在其中绘制自定义拖动预览组件。请注意,如果我们愿意,我们可以在拖动层上绘制一个完全不同的组件。这不仅仅是一个截图。false
从返回canDrag
,则将询问其父级,直到找到并激活可拖动源。只有激活拖动源将拥有beginDrag()
和 endDrag()
调用。hover
处理程序中重新排序数据。drop()
处理程序中移动卡片,而是在拖动源的endDrag()
处理程序中进行。这让我们检查monitor.didDrop()
并恢复拖动操作,如果卡片被放置在其容器之外。requestAnimationFrame
回调中更新状态,它可以处理几千个项目而不会滞后。在那之后,你最好使用像fixed-data-table这样的虚拟列表 。render
函数中的拖动预览节点。① ② ③④⑤⑥⑦⑧⑨