之前写过一篇 react-dnd 用法的文章,里面写的可能比较啰嗦了,但是内容比较详细,很多 API 都罗列了出来。目前 React Hooks 出来了,react-dnd 也做了对应的更新,所以本篇使用 React Hooks + TypeSscript 对 react-dnd 用法重新梳理一下。
前言
当前版本介绍
react
:16.9.0
react-dom
:16.9.0
typescript
:3.5.3
react-dnd
:9.3.4
react-dnd-html5-backend
:9.3.4
脚手架使用的 create-react-app
,全局安装 create-react-app
的命令:npm i create-react-app -g
环境搭建
- 创建脚手架:
npx create-react-app react-dnd-hooks --typescript
- 安装
react-dnd
:yarn add react-dnd
- 安装
react-dnd-html5-backend
:yarn add react-dnd-html5-backend
为了方便统一个标识,'拖拽组件' 使用 'drag 组件' 代替,'目标接收组件' 使用 'drop 组件' 代替
一、用 DndProvider 将根节点包裹起来
想要使用 react-dnd
进行拖拽操作,需要用 DndProvider
标签将根节点包裹起来,并传入一个 backend
参数:
index.tsx
文件
import React from 'react';
import ReactDOM from 'react-dom';
import { DndProvider } from 'react-dnd';
import HTMLBackend from 'react-dnd-html5-backend'
import './index.css';
import App from './App';
ReactDOM.render(
<DndProvider backend={ HTMLBackend }>
<App />
</DndProvider>,
document.getElementById('root'));
二、让元素可以动起来
比如现在有一个 Box 组件,我们想将其进行拖拽,此时我们先声明一下这个组件
创建 Box 组件
Box.tsx
import React, { CSSProperties } from 'react';
const style: CSSProperties = {
width: 200,
height: 50,
lineHeight: '50px',
background: 'pink',
margin: '30px auto'
}
const Box = () => {
return (
<div style={ style }>可拖拽组件 Box</div>
)
}
export default Box;
这个时候我们使用鼠标按住 Box 组件,发现还没有办法拖拽 Box 组件
让 Box 组件动起来
使用 react-dnd
提供的 useDrag
, 返回的第一个值是 collect 方法返回的对象(这里没有使用,所以省略了),返回的第二个值是一个 ref,将其赋值给想要拖拽的元素即可实现组件拖动。
Box.tsx
...
import { useDrag } from 'react-dnd';
...
const Box = () => {
// 使用 useDrag
const [, drager] = useDrag({
item: { type: 'Box' }
})
return (
// 将第二个参数赋值给 ref
<div ref={ drager } style={ style }>可拖拽组件 Box</div>
)
}
export default Box;
这个时候我们就可以使用鼠标来回拖拽 Box
组件了,但是只能拖拽,还没有地方能够接收感应到 Box
组件,下面我们来看看怎么操作。
三、创建 Dustbin 组件用来接收 drag 组件
使用 react-dnd
提供的 useDrop
,返回的第一个值是 collect 方法返回的对象,返回的第二个值是一个 ref,将其赋值给想要接收的 drop
组件即可感应到 drag 组件。
Dustbin
组件:
import React, { CSSProperties } from 'react';
import { useDrop, DropTargetMonitor } from 'react-dnd';
const style: CSSProperties = {
width: 400,
height: 400,
margin: '100px auto',
lineHeight: '60px',
border: '1px dashed black'
}
const Dustbin = () => {
// 第一个参数是 collect 方法返回的对象,第二个参数是一个 ref 值,赋值给 drop 元素
const [collectProps, droper] = useDrop({
// accept 是一个标识,需要和对应的 drag 元素中 item 的 type 值一致,否则不能感应
accept: 'Box',
// collect 函数,返回的对象会成为 useDrop 的第一个参数,可以在组件中直接进行使用
collect: (minoter: DropTargetMonitor) => ({
isOver: minoter.isOver()
})
})
const bg = collectProps.isOver ? 'deeppink' : 'white';
const content = collectProps.isOver ? '快松开,放到碗里来' : '将 Box 组件拖动到这里'
return (
// 将 droper 赋值给对应元素的 ref
<div ref={ droper } style={{ ...style, background: bg }}>{ content }</div>
)
}
export default Dustbin;
温馨提示:记得将 Box 和 Dustbin 组件引用到 App.tsx 组件里使用。
四、效果图
五、其他常用 API 和注意事项
-
drag
组件常用的属性:-
item
:是一个对象,必须要有一个type
属性 -
begin(mintor: DragSourceMonitor)
:组件开始拖拽,必须返回一个对象包含type
属性,会覆盖item
属性返回的对象,会被传入 drop 组件 hover 和 drop 方法的第一个参数 -
end(item, mintor: DragSourceMonitor)
: 组件停止拖拽时触发,item
是drop
组件在drop
方法执行时返回的对象,等同于mintor.getDropResult()
的值
-
-
drop
组件常用的属性-
accept
:字符串,必须和对应drag
组件的item
属性中的type
值一致 -
hover(item, minoter: DropTargetMonitor)
:drag
组件在drop
组件上方hove
时触发 -
drop(item, minoter: DropTargetMonitor)
:drag
组件拖拽结束后,放到drop
组件时触发,返回的值会作为参数传递给drag
组件end
方法的第一个参数
-
-
让组件既可以被拖拽也可以接收拖拽元素
import React, { useRef } from 'react';
import { useDrag, useDrop } from 'react-dnd'
const Card = () => {
const ref = useRef<HTMLDivElement>(null);
const [, drop] = useDrop({
accept: 'Card',
});
const [, drag] = useDrag({
item: { type: 'Card' }
});
// 使用 drag 和 drop 包装 ref
drag(drop(ref));
// 将变量 ref 传给元素的 ref 即可
return (<div ref={ ref }>既可以被拖动也可以接收拖动组件</div>)
}
-
drag
组件想传递一些数据出去- 直接使用 item 属性传:
const [, drag] = useDrag({ item: { type: 'Card', id: 1, name: 'card1', kind: 'Card } });
- 利用 begin 方法传值:
注意:begin 方法的返回值会将 item 属性覆盖,所以一定要传 type 属性
const [, drag] = useDrag({ item: { type: 'Card' }, begin(mintor: DragSourceMonitor) { return { type: 'Card', id: 1, name: 'card1', kind: 'Card } } });
- 之后在
dropTarget
接收组件中就可以在 hover 或者 drop 方法的第一个参数中获取到,或者使用DropTargetMonitor
的getItem()
函数获取。
-
想要获取到
drag
组件或者drop
组件的一些状态信息drag
组件:
// collect 函数返回的对象会赋给 useDrop 的第一个参数 collectProps,可以在组件中直接进行使用 const [collectProps, drag] = useDrag({ item: { type: 'Card', id: 1, name: 'card1', kind: 'Card }, collect: (minoter: DropTargetMonitor) => ({ isOver: minoter.isOver(), }) });
drop
组件:
// collect 函数返回的对象会赋给 useDrop 的第一个参数 collectProps,可以在组件中直接进行使用 const [collectProps, droper] = useDrop({ accept: 'Box', collect: (minoter: DropTargetMonitor) => ({ isOver: minoter.isOver(), }) })
- 更多状态信息的 API 可以查看我之前文章 中的 DragSourceMonitor 相关 API 和 DropTargetMonitor 相关 API
六、一个更复杂的 Demo 演示
一个可以进行拖拽放置并进行拖拽排序的例子,有兴趣的小伙伴可以看看。
效果图
Demo 地址
欢迎 Star! 欢迎 Fork!