浅说React-Dnd的使用

news2024/11/18 5:38:47

前言(学习原因)

近期在工作中遇到一个新的需求:具体大概效果看下面.gif

当时看到这个需求经师兄提醒知道了React-Dnd,想到了表格 Table - Ant Design里有一个可拖拽表格的效果,照搬到了项目里发现不太能满足我们的需求,就去搜了一些资料了解了React-Dnd的一下原理

React-Dnd

React DnD 是一个专注于数据变更的 React 拖拽库, 对 HTML拖拽API进行了封装,通俗的将,你拖拽改变的不是页面视图,而是数据。React DnD 不提供炫酷的拖动体验,而是通过帮助我们管理拖拽中的数据变化,再由我们根据这些数据进行渲染。

这样子在处理拖拽的时候就可以关注于数据方面的变化,而不用费心去维护拖拽中的一些中间状态,更不用自己去添加,移除事件。

先知道咋安装

tnpm(npm) install react-dnd -S // react-dnd包,其核心包 
tnpm(npm) install react-dnd-html5-backend -S // 拖拽的底层实现所需要的库 

除了react-dnd外还需要react-dnd-html5-backend这个包,它存在才会允许 React DnD 底层的 HTML5 拖放 AP,使得HTML 拖放界面使应用程序能够在浏览器中使用拖放功能

引入使用

//引入
import {DndProvider, useDrag, useDrop} from 'react-dnd'
import {HTML5Backend} from 'react-dnd-html5-backend'

三个核心点

通过使用React DnD这个库,我认为里面最有用的部分包含一个组件和两个Hook API,它们分别是:

  • DndProvider组件
  • useDrag函数
  • useDrop函数

React-DnD实现

DndProvider注入

如果想要使用 React DnD,首先需要在外层元素上加一个 DndProvider,得先声明一下拖拽的对象

比较官方解释:

DndProvider组件为您的应用程序提供React-DnD功能。必须通过backend参数将其注入后端,但是也可以将其注入window对象。

  • backend: 必填,一个 React DnD 后端,实现DnD的方式,目前官方文档有三个,分别为:react-dnd-html5-backend、react-dnd-touch-backend、react-dnd-test-backend,但是常用的还是react-dnd-html5-backend,或者也可以自己写backend后端。。
  • context: 选填,用户配置后端的上下文,这取决于后端的实现。
  • options: 选填,配置后端对象,自定义时可以传入backend。

比较通俗解释:

DndProvider

本质是一个由 React.createContext 创建一个上下文的容器(组件),用于控制拖拽的行为,数据的共享。DndProvider 的入参是一个 Backend。

Backend 是什么呢?

React DnD 抽象了后端的概念,我们可以使用 HTML5 拖拽后端,也可以自定义 touch、mouse 事件模拟的后端实现,后端主要用来抹平浏览器差异,处理 DOM 事件,同时把 DOM 事件转换为 React DnD 内部的 redux action。

//引入
import {DndProvider, useDrag, useDrop} from 'react-dnd'
import {HTML5Backend} from 'react-dnd-html5-backend'
//DndProvider注入
export const DndCheckBoxGroup = (props) => {
  const options = props.options
  const setOptions = props.setOptions
  const value = props.value
  const onChange = props.onChange
  return <OptionsContextProvider options={options} setOptions={setOptions} value={value} onChange={onChange}>
    <DndProvider backend={HTML5Backend}>
      //此处将放拖拽相关内容 
      <DndCheckbox/>
    </DndProvider>
  </OptionsContextProvider>
}

DndCheckbox组件

写一个组件把列表渲染一下,可以看出我们要将实现的拖拽效果应用到DraggableCheckbox组件上

const DndCheckbox = () => {
    const {options, value, onChange} = useContext(OptionsContext);
    return <Checkbox.Group value={value} onChange={onChange}>
        {
            options.map((item, index) => {
                return <DraggableCheckbox key={item.value} item={item} index={index}/>
            })
        }
    </Checkbox.Group>
}

useDrag 声明拖动源

一个元素想要被拖拽需要了解的就是从什么位置进行拖拽,该库提供了useDrag hook API,该元素可以让一个DOM元素实现拖拽效果。

代码格式

import { useDrag } from 'react-dnd' 
    /** 
     * 返回的参数 
     * collected:一个对象,包含从collect函数收集的属性,如果collect未定义函数,则返回一个空对象 
     * drag:拖动器的连接器功能,必须附加到DOM的可拖动部分 
     * dragPreview:用于拖动预览的连接器功能,可以附加到DOM的预览部分 
     */ 
const [collected, drag, dragPreview] = useDrag(() => ( () => ({
        // 只有drop和此值相同才可以进行放置 
            type,
        // 描述要拖动的数据 
            item,
         // 监听功能 
            collect: (monitor,props) => ({
                isDragging: monitor.isDragging()
            })
        }), [deps])

入参

  • spec 规范对象或创建规范对象的函数,关键内容包括:
    • item: 必填。一个普通的JavaScript对象,描述了要拖动的数据。这是可用于放置目标的有关拖动源的唯一信息
    • type: 必填,并且必须是字符串,ES6符号。useDrop的accept为相同类型,target才会对该项作出反应
    • isDragging(monitor):选填。默认情况下,只有启动拖动操作的拖动源才被视为拖动
    • options:可选的,一个简单对象
    • collect:选填,收集功能,它接收两个参数,monitor和props。(useDrag也有)

另外还有一些像拖动开始时begin(monitor)、结束时end(item, monitor)、是否允许拖动canDrag(monitor)等相关内容,可以在更多学习链接里学习。

  • deps

用于记忆的依赖数组。这类似于内置的useMemo钩子。默认值为函数规范的空数组,以及包含对象规范的规范的数组。

返回值

  • arguments[0]: 一个对象,其中包含从collect函数收集的属性。如果collect未定义函数,则返回一个空对象。
  • arguments[1]: DragSource Ref,拖动源的连接器功能。这必须附加到DOM的可拖动部分。
  • arguments[2]: DragPreview Ref,用于拖动预览的连接器功能。这可以附加到DOM的预览部分。

useDrop 声明放置源

为了将内容放置到目标位置,提供了useDrop Hooks函数,

useDrop将放置目标元素和DnD系统连接起来。通过将规范的拖拽对象作为入参传入useDrop,你可以定义放置目标接受的数据项accept,使用哪些collect等等。该函数返回一个数组,其中包含一个要附加到Drop Target节点的ref和collected的props。

代码格式

const [collectedProps, drop] = useDrop(() => (
  //spec
           {
   //  此放置目标将仅对于指定类型的拖动源产生的项目作出反应 
            accept,
    // 当兼容项目放在目标时调用 
            drop: (item) => {
            },
      // 监听功能 
            collect: monitor => ({
                //isOver: !!monitor.isOver(),
               // 是否重叠 
                isOver: monitor.isOver(), 
                // 是否可以放置 
                canDrop: monitor.canDrop(), 
            }),
        }),
        [deps]
  }))

入参

  • spec 规范对象或创建规范对象的函数,关键内容包括:
    • accept: 必填。字符串,ES6符号,其中一个的数组或返回给定组件的其中一个的函数props。此放置目标将仅对由指定类型的拖动源产生的项目作出反应,比如说你useDrag拖动的是复选框,useDrop接收到也要是复选框。
    • options: 选填。一个普通的对象。
    • drop(item, monitor): 选填。可选的,当兼容项目放在目标时被调用;
    • collect: 可选的,监听功能

另外还有一些像拖动停止时end(item, monitor)、是否允许拖动canDrag(monitor),options等相关内容,可以在更多学习链接里学习。

  • deps

deps用于记忆的依赖数组。这类似于内置的useMemo钩子。默认值为函数规范的空数组,以及包含对象规范的规范的数组(坑踩在这里)

返回值

  • arguments[0]: 一个对象,其中包含从collect函数收集的属性。如果collect未定义函数,则返回一个空对象。
  • arguments[1]: 拖动源的连接器功能。这必须附加到DOM的可拖动部分。

写到这里,可以看出实现拖拽效果,需要有三个要点,首先是用Dnd来包裹我们需要的拖拽的ref元素,再声明拖动的它(拖动源)和可以放置它的他(放置源)

具体实现

DndCheckboxGroupExample组件定义了拖放项,使用 useDrag 和 useDrop 包裹,调用父组件传过来的拖放的索引to,moveOption 方法来处理拖拽。

const CHECKBOX_TYPE = 'dndCheckbox';

function DraggableCheckbox(props) {
    const ref = useRef(null);
    const to = props.index
    const {moveOption} = useContext(OptionsContext);

    const [, drag] = useDrag(
        () => ({
            type: CHECKBOX_TYPE,
            item: {index: to},
            collect: (monitor) => ({
                isDragging: monitor.isDragging()
            })
        }), [to]
    )
    const [, drop] = useDrop(() => ({
            accept: CHECKBOX_TYPE,
            drop: (item) => {
                let from = item.index;
                moveOption(from, to)
            },
            collect: monitor => ({
                isOver: !!monitor.isOver(),
            }),
        }),
        [to, moveOption]
    )

    drop(drag(ref))

    if(typeof props?.item ==='string'){
        return <span ref={ref}><Checkbox value={props.item}>{to}{props.item}</Checkbox> </span>
    }
    else{
        return <span ref={ref}><Checkbox value={props.item.value}>{to}{props.item.label}</Checkbox> </span>
    }
}
  • 绑定ref,获取到我们拖动的组件(小小坑,封装的组件不能获取到直接获取ref,需要在外面套一层容器)
  • 拿到我们拖动的数据在列表中index(to)
  • 通过useDrag来调用moveOption (from,to)方法(这个方法很关键)写在上面的OptionsContextProvider

组件中传递出来

/**
 * useCallback 用来返回一个函数,在父子组件传参或者通用函数封装中
 * 返回的函数a会根据b的变化而变化,如果b始终未发生变化,a也不会重新生成,避免函数在不必要的情况下更新。
 * @param {number} oldIndex - 旧的索引
 * @param {number} newIndex - 新的索引
 * @returns {function} - 返回一个函数
 */
const moveOption = useCallback((oldIndex, newIndex) => {
    // 获取需要移动的元素
    const movedItem = options[oldIndex]
    // 根据旧的索引过滤出新的元素
    const sortedOptions = options.filter((item, index) => {
        return index!== oldIndex
    })
    // 将需要移动的元素插入到新的位置
    sortedOptions.splice(newIndex, 0, movedItem)
    // 根据新的元素排序
    const sortedValue = value.sort((a,b)=> {
        return sortedOptions.findIndex((item)=>item.value === a) -
            sortedOptions.findIndex((item)=>item.value === b)
    })
    // 更新选项
    onChange(sortedValue)
    // 更新选项
    setOptions(sortedOptions)
}, [options, setOptions, value])

踩坑教训

刚开始小菜的useDrop依赖只写了

于是发现自己更新的数据,拖拽的数据会变成初始默认选中的值,后选的选中态会取消,描述不出来,大家看一下截图吧

可以看出拖拽了index为2的复选框,应该移动到第一位

然而拖拽后

恢复到了默认(哭了哭了)

遇到错就要解决啊,然后换方法写useMemo,useEffect,结局可想而知。。。。一度怀疑自己的数据传错了,debug,debug,解决不出来,就去重新翻了文档,发现deps这个地方,也要把moveOption放到依赖里进行监听,及时更新数据,不然数据总会恢复到默认值。。。

demo:https://github.com/Py-spj/syxDemo

更多学习(参考)链接:

  • React DnD
  • 拖拽组件:React-DnD用法及源码解析 - 掘金

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/826754.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

逆波兰表达式求值

给你一个字符串数组 tokens &#xff0c;表示一个根据 逆波兰表示法 表示的算术表达式。 请你计算该表达式。返回一个表示表达式值的整数。 注意&#xff1a; 有效的算符为 、-、* 和 / 。每个操作数&#xff08;运算对象&#xff09;都可以是一个整数或者另一个表达式。两个…

语义检索系统【全】:基于Milvus+ERNIE+SimCSE+IBN实现学术文献语义检索系统完整版

搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术细节以及项目实战(含码源) 专栏详细介绍:搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术…

PCB添加二维码

PCB添加二维码 1、PCB上添加二维码 在PCB上添加二维码&#xff0c;可以清楚的展示PCB板子的信息&#xff0c;包括&#xff1a;制板人员、时间以及这个板子所用的仪器。 2、如何在PCB上添加二维码&#xff08;错误示例&#xff09; 首先说一种错误的方法&#xff0c;试了很多…

RS232转Profinet网关rs232串门转网门接法

在工业自动化领域&#xff0c;如何将扫码枪与PLC连接一直是一个重要的问题。而今天&#xff0c;我们将通过一个案例来展示如何通过RS232转Profinet网关&#xff0c;将X-9300扫码枪接入到PLC1200工业以太网总线上。在这个过程中&#xff0c;我们将会用到捷米的RS232自由协议转Pr…

文件系统的基本认知笔记

1.什么是文件系统 常规认知&#xff1a;Linux下的根目录文件系统是操作系统用于明确存储设备&#xff08;常见的是磁盘&#xff0c;也有基于NAND Flash的固态硬盘&#xff09;或分区上的文件的方法&#xff0c;即在存储设备上组织文件的方法&#xff0c;这种所谓的方法就是文件…

Linux【网络编程】之深入理解TCP协议

Linux【网络编程】之深入理解TCP协议 TCP协议TCP协议段格式4位首部长度---TCP报头长度信息 TCP可靠性&#xff08;确认应答&#xff09;&& 提高传输效率确认应答(ACK)机制32位序号与32为确认序号 16位窗口大小---自己接收缓冲区剩余空间的大小16位紧急指针---紧急数据处…

Unity 使用SharpZipLib解压时报错

报错信息&#xff1a; NotSupportedException: Encoding 936 data could not be found. Make sure you have correct international System.Text.Encoding.GetEncoding (System.Int32 codepage) ICSharpCode.SharpZipLib.Zip.ZipConstants.ConvertToString。 出现问题分析&…

三种简单易用的制作符合要求的证件照片的方法

在生活和学习中&#xff0c;我们经常需要上传一些证件照片。但是在准备上传之前&#xff0c;我们可能会发现底色不正确&#xff0c;这会导致无法通过审核。重新拍照既费时间又浪费金钱&#xff0c;这让人感到非常困扰。然而&#xff0c;我们可以借助一些方法和工具来轻松更改证…

Java作业1

1.编写程序数一下 1到 100 的所有整数中出现多少个数字9 十位 n/10 个位 n%10 public static void main(String[] args) {int count 0;for (int i 1; i < 100; i) {if(i / 10 9){count;}if(i%109){count;}}System.out.println(count);} 2.给定一个数字&#xff0c;判…

过滤器,监听器与拦截器的区别

过滤器&#xff0c;监听器与拦截器的区别 ​ 过滤器和监听器不是Spring MVC中的组件&#xff0c;而是Servlet的组件&#xff0c;由Servlet容器来管理。拦截器是Spring MVC中的组件&#xff0c;由Spring容器来管理 ​ Servlet过滤器与Spring MVC 拦截器在Web应用中所处的层次如…

身为测试人“我“不再背锅,完整一套软件测试流程汇总...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 关于测试流程&…

FPGA优质开源模块 - SRIO

本文介绍一个FPGA常用模块&#xff1a;SRIO&#xff08;Serial RapidIO&#xff09;。SRIO协议是一种高速串行通信协议&#xff0c;在我参与的项目中主要是用于FPGA和DSP之间的高速通信。有关SRIO协议的详细介绍网上有很多&#xff0c;本文主要简单介绍一下SRIO IP核的使用和本…

【论文精读2】用于多文档摘要生成的层次Transformer方法

前言 论文分享 来自2019ACL的多文档摘要生成方法论文&#xff0c;作者来自英国爱丁堡大学&#xff0c;引用数310 Hierarchical Transformers for Multi-Document Summarization 代码地址hiersumm 多文档摘要抽取的难点在于没有合适的数据集&#xff0c;同时过长的文档文本也导…

剑指 Offer 55 - II. ! 平衡二叉树

剑指 Offer 55 - II. 平衡二叉树 输入一棵二叉树的根节点&#xff0c;判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1&#xff0c;那么它就是一棵平衡二叉树。 来自力扣K神的解法1&#xff0c;真的是太巧妙了&#xff01; 方法recur检查以nod…

什么是自动化测试框架?自动化测试框架有哪些?

一、自动化测试 1、为什么要做自动化测试&#xff1f; 自动化测试就是把以人为驱动的测试行为转化为机器执行的一种过程&#xff0c;即模拟手工测试的步骤&#xff0c;通过执行测试脚本自动地测试软件自动化测试就是程序&#xff08;脚本&#xff09;测试程序&#xff0c;使用…

LeNet卷积神经网络-笔记

LeNet卷积神经网络-笔记 手写分析LeNet网三卷积运算和两池化加两全连接层计算分析 基于paddle飞桨框架构建测试代码 #输出结果为&#xff1a; #[validation] accuracy/loss: 0.9530/0.1516 #这里准确率为95.3% #通过运行结果可以看出&#xff0c;LeNet在手写数字识别MNIST验证…

如何开启一个java微服务工程

安装idea IDEA常用配置和插件&#xff08;包括导入导出&#xff09; https://blog.csdn.net/qq_38586496/article/details/109382560安装配置maven 导入source创建项目 修改项目编码utf-8 File->Settings->Editor->File Encodings 修改项目的jdk maven import引入…

【C++】类和对象——拷贝构造函数、运算符重载、日期类实现、const成员、取地址操作符重载

目录 拷贝构造函数运算符重载日期类实现const成员取地址及const取地址操作符重载 拷贝构造函数 拷贝构造函数&#xff1a;只有单个形参&#xff0c;该形参是对本类类型对象的引用(一般常用const修饰)&#xff0c;在用已存在的类类型对象创建新对象时由编译器自动调用。 拷贝构…

SOLIDWORKS 钣金零件怎么画?

一、SOLIDWORKS 钣金功能介绍 SOLIDWORKS 是一款广泛应用于机械设计领域的 CAD 软件&#xff0c;其钣金功能可以帮助用户快速创建钣金件的 3D 模型。钣金折弯是一种常见的加工方式&#xff0c;可以将平面材料通过弯曲变形成为所需形状。 二、如何使用 SOLIDWORKS 钣金功能 步骤…

shell清理redis模糊匹配的多个key

#!/bin/bash# 定义Redis服务器地址和端口 REDIS_HOST"localhost" REDIS_PORT6380# 获取匹配键的数量 function get_matching_keys() {local key_pattern"$1"redis-cli -h $REDIS_HOST -p $REDIS_PORT -n 0 KEYS "$key_pattern" }# 删除匹配的键 …