用 React Hooks 的方式使用 react-dnd

20,365 阅读5分钟

之前写过一篇 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-dndyarn add react-dnd
  • 安装 react-dnd-html5-backendyarn 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 组件里使用。


四、效果图

Dustbin 效果图


五、其他常用 API 和注意事项

  • drag 组件常用的属性:

    • item:是一个对象,必须要有一个 type 属性

    • begin(mintor: DragSourceMonitor):组件开始拖拽,必须返回一个对象包含 type 属性,会覆盖 item 属性返回的对象,会被传入 drop 组件 hover 和 drop 方法的第一个参数

    • end(item, mintor: DragSourceMonitor): 组件停止拖拽时触发,itemdrop 组件在 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 方法的第一个参数中获取到,或者使用 DropTargetMonitorgetItem() 函数获取。
  • 想要获取到 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(),
          })
      })
    

六、一个更复杂的 Demo 演示

一个可以进行拖拽放置并进行拖拽排序的例子,有兴趣的小伙伴可以看看。

效果图

拖拽并排序预览

Demo 地址

react-dnd-hooks-demo

欢迎 Star! 欢迎 Fork!


七、资源链接