Vue.Draggable 踩坑:add 事件与 change 事件中 newIndex 字段不同之谜

背景

  最近在弄自定义表单,需要拖动组件进行表单设计,所以用到了 Vue.Draggable(中文文档)。Vue.Draggable 是一款基于 Sortable.js 实现的 vue 拖拽插件,文档挺简单的,用起来也方便,但没想到接下来给我遇到了灵异事件…

坑的表现

  当我写完了由配置对象到组件的渲染逻辑之后,便开始了阶段性测试。我先是拖入了一个输入框,它正常的渲染了出来,并且各项功能都很正常。
在这里插入图片描述

  然后我又拖了个文本域进去,随手把它放在输入框的下面。

在这里插入图片描述

  结果意想不到的事发生了,文本域居然跑到了输入框的上面去了,我惊呆了…

在这里插入图片描述
  印象中拖入放置时元素在列表中的位置是 Vue.Draggable 自己维护的啊,我没做什么控制,怎么可能出问题呢?满脑子疑惑的我又拖了个文本域放在输入框下面,结果它有一次惊呆了我,它正常了,没有跑到输入框上面去…

  我刷新页面打算重新试一下。

    ● 第一步,拖入一个输入框,正常。
    ● 第二步,拖入一个文本域放在输入框下面,不正常,跑上面去了。
    ● 第三步,再次拖入一个文本域放在输入框下面,正常。

  好家伙,看来按这个步骤是百分百重现了。老老实实去检查代码,确认没有手动维护过 Vue.Draggable 中的 list。在 add 事件中打印 event.newIndex (以下称 addEvent.newIndex),发现 addEvent.newIndex 的值是正常的,但是却与新增元素在 list 中的下标不一致,又在 change 事件中打印 newIndex (以下称 changeEvent.newIndex),发现 changeEvent.newIndex 却是指向新元素在 list 中的位置。

  但是 changeEvent.newIndex 的值不对啊!它应该跟 addEvent.newIndex 一样才对啊!文本域应该在输入框的下面才对啊!啊啊啊!!!难道我发现了 Vue.Draggable 的 BUG?

填坑

  结论直达

  两个事件中的 newIndex 完全是由 Vue.Draggable 自身维护的,要想找到导致它俩不同的原因,只能去看看 Vue.Draggable 的源码了。于是我拉取了 Vue.Draggable 的源码,打算来研究一下。值得庆幸的是 Vue.Draggable 的源码很少,只有 400 多行,读起来比较简单。

  我很快找到了下面处理 add 事件的代码。

// Vue.Draggable 源码// onDragAdd 方法是 Draggable 组件内部方法,它调用之后才会 emit Draggable 组件的 add 事件
// 可以在源码中搜索 delegateAndEmit 查找绑定事件的位置
// vue 组件 methods 选项中的方法
onDragAdd(evt) {const element = evt.item._underlying_vm_;if (element === undefined) {return;}removeNode(evt.item);// evt.newIndex 是 add 事件中的 newIndexconst newIndex = this.getVmIndex(evt.newIndex);this.spliceList(newIndex, 0, element);this.computeIndexes();// added.newIndex 是 change 事件中的 newIndexconst added = { element, newIndex };this.emitChanges({ added });
},

  从上面的代码中可以看出,change 事件中的 newIndexadd 事件中的 newIndex 经由 this.getVmIndex() 方法加工而来的。那么我们看一下 this.getVmIndex() 方法做了什么加工导致了它们的不一样。

// Vue.Draggable 源码// added.newIndex 依赖于 this.visibleIndexes (一个数组),当新元素的下标小于 this.visibleIndexes 的长度减一时,返回 this.visibleIndexes 的长度,否则返回 this.visibleIndexes 中下标为 domIndex 的值
/*** vue 组件 methods 选项中的方法,计算并返回 change 事件中的 newIndex* @param {number} domIndex - add 事件中的 newIndex* @returns {number}*/
getVmIndex(domIndex) {const indexes = this.visibleIndexes;const numberIndexes = indexes.length;return domIndex > numberIndexes - 1 ? numberIndexes : indexes[domIndex];
},

  getVmIndex() 方法用于计算 changeEvent.newIndex。它的参数 domIndex 即为 addEvent.newIndex

  从上面的代码可以看出 changeEvent.newIndex 还依赖于 this.visibleIndexes(一个数组),当 domIndex(新元素的下标)大于 this.visibleIndexes 的长度 - 1(最后一个元素的下标)时,changeEvent.newIndexthis.visibleIndexes的长度(最后一个元素的下标 + 1),否则为 this.visibleIndexes 中下标为 domIndex 的值。

  看来还要弄明白 this.visibleIndexes 是什么,下面的代码说明了 this.visibleIndexes 的由来。

// vue 组件 methods 选项中的方法,
computeIndexes() {this.$nextTick(() => {// 这个 computeIndexes 并不是在 methods 中声明的,因此调用时没有使用 thisthis.visibleIndexes = computeIndexes(this.getChildrenNodes(),this.rootContainer.children,this.transitionMode,this.footerOffset);});
},/*** 计算 this.visibleIndexes 列表* @param {Array<VNode>} slots - isTransition 为 true 时,表示 TransitionGroup 的默认插槽,否则表示 draggable 组件的默认插槽* @param {Array<Node>} children - isTransition 为 true 时,表示 TransitionGroup 子元素列表,否则表示 draggable 组件子元素列表* @param {boolean} isTransition - 是否使用了 TransitionGroup 组件* @param {number} footerOffset - footer 插槽根元素的个数,没有使用 footer 插槽时为 0* @returns*/
function computeIndexes(slots, children, isTransition, footerOffset) {if (!slots) {return [];}const elmFromNodes = slots.map(elt => elt.elm);const footerIndex = children.length - footerOffset;// rawIndexes 列表表示显示的节点,其虚拟节点在 slots 中的位置const rawIndexes = [...children].map((elt, idx) => {return idx >= footerIndex ? elmFromNodes.length : elmFromNodes.indexOf(elt);});// 如果使用了 TransitionGroup 组件,则将 children 中有而 slots 中没有的过滤掉return isTransition ? rawIndexes.filter(ind => ind !== -1) : rawIndexes;
}

  由上面的代码可以看出 rawIndexes 数组表示:显示的节点,其虚拟节点在 slots 中的位置。rawIndexes 元素的下标表示节点在 children 中的下标,元素的值表示节点在 slots 中的下标(如果节点在 footer 之后,则值为 slots 的长度)。

  现在我们再来看 getVmIndex() 方法,this.visibleIndexes 是由 slotschildren 维护的,而它决定了 changeEvent.newIndex 的值,所以影响 changeEvent.newIndex 的根本因素就是 slotschildren

  找到了根本因素接下来就简单了。我重复执行出现问题的操作步骤,然后在这个过程中打印 slotschildren,我惊讶的发现当我向 Vue.Draggable 第一次拖入输入框组件时,slotschildren 的长度居然不一样!children 是空的, 而 slots 的长度虽然正常,但其中的虚拟节点的 elm 属性却是 undefined

  看到这里我恍然大悟,正是 slotschildren 异常的值导致了 changeEvent.newIndex 的计算错误,那么是什么导致了它们值的异常呢?也许你有注意到 slots 虽然长度正常,但其中的虚拟节点的 elm 属性却是 undefined

  是的,没错,正是因为输入框组件采用了懒加载的方式进行引入,而导致的这个诡异的问题!

  万万没想到,组件的引入方式居然还会导致奇怪的问题出现!

总结

  所以,如果想在 Vue.Draggable 中使用自定义组件,那么千万不要使用懒加载的方式引入这些组件!

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

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

相关文章

PySide/PYQT如何用Qt Designer和代码来设置文字属性,如何设置文字颜色?

文章目录 📖 介绍 📖🏡 环境 🏡📒 实现方法 📒📝 Qt Designer设置📝 代码📖 介绍 📖 本人介绍如何使用Qt Designer/代码来设置字体属性(包含字体颜色) 🏡 环境 🏡 本文使用Pyside6来进行演示📒 实现方法 📒 📝 Qt Designer设置 首先打开Qt De…

如何在ModelScope社区魔搭下载所需的模型

本篇文章介绍如何在ModelScope社区下载所需的模型。 若您需要在ModelScope平台上有感兴趣的模型并希望能下载至本地&#xff0c;则ModelScope提供了多种下载模型的方式。 使用Library下载模型 若该模型已集成至ModelScope的Library中&#xff0c;则您只需要几行代码即可加载…

mysql8安装和驱动jar包下载

方式一&#xff1a;基于docker安装 下拉镜像 docker pull mysql:8.0.21 启动镜像 docker run -p 3307:3306 --name mysql -e MYSQL_ROOT_PASSWORDhadoop -d mysql:8.0.21 启动成功后&#xff0c;进入容器内部拷贝配置文件&#xff0c;到宿主主机 docker cp mysql:/etc/mysql…

人工智能基础——图像认知与OpenCV

人工智能的学习之路非常漫长&#xff0c;不少人因为学习路线不对或者学习内容不够专业而举步难行。不过别担心&#xff0c;我为大家整理了一份600多G的学习资源&#xff0c;基本上涵盖了人工智能学习的所有内容。点击下方链接,0元进群领取学习资源,让你的学习之路更加顺畅!记得…

pandas笔记:读写excel

1 读excel read_excel函数能够读取的格式包含&#xff1a;xls, xlsx, xlsm, xlsb, odf, ods 和 odt 文件扩展名。 支持读取单一sheet或几个sheet。 1.0 使用的数据 1.1 主要使用方法 pandas.read_excel(io, sheet_name0, header0, namesNone, index_colNone, usecolsNon…

pyqt环境搭建

创建虚拟环境 # 用管理员身份运行 conda create --prefixE:\Python\envs\pyqt5stu python3.6 # 激活虚拟环境 conda activate E:\Python\envs\pyqt5stu # 退出虚拟环境 conda deactivate安装包 pip install PyQt5 -i https://pypi.douban.com/simple pip install PyQt5-tools…

如何用Excel软件制作最小二乘法①

一、用自带的选项&#xff08;不推荐&#xff09;&#xff0c;因为感觉只是近似&#xff0c;虽然结果一样 1.在Excel中输入或打开要进行在excel中输入或打开要进行最小二乘法拟合的数据&#xff0c;如图所示。 2.按住“shift”键的同时&#xff0c;用鼠标左键单击以选择数据&a…

【4】Gradle-快速入门使用【Gradle多模块项目详解】

目录 【4】Gradle-快速入门使用【Gradle多模块项目详解】创建多项目构建添加子项目命名建议 项目依赖项项目路径不同模块的build.gradle配置 子项目之间共享构建逻辑公约插件跨项目配置buildSrc开发公约插件 调整多模块项目配置修改项目树的元素 了解Gralde配置时间和执行时间并…

红黑树,AVLTree树(平衡二叉树)迭代器原理讲解

红黑树&#xff0c;AVLTree树底层实现逻辑都是平衡二叉树&#xff08;AVLTree高度平衡&#xff0c;红黑树以某种规则平衡&#xff09;&#xff0c;但终究不像链表的迭代器那样逻辑简单。 简单叙述以下&#xff0c;二叉树上面迭代器的运行逻辑&#xff0c;根据下面的图&#xff…

Nginx:如何实现一个域名访问多个项目

1. 背景介绍 最近在多个项目部署中遇到这样一个问题&#xff0c;一个域名如何实现多个项目的访问。因为不想自己单独去申请域名证书和域名配置&#xff0c;便想到了这个方案&#xff0c;结合Nginx的location功能实现了自己的需求&#xff0c;便记录下来。示例中是以项目演示&a…

从TCP到Socket,彻底理解网络编程是怎么回事

进行程序开发的同学&#xff0c;无论Web前端开发、Web后端开发&#xff0c;还是搜索引擎和大数据&#xff0c;几乎所有的开发领域都会涉及到网络编程。比如我们进行Web服务端开发&#xff0c;除了Web协议本身依赖网络外&#xff0c;通常还需要连接数据库&#xff0c;而数据库连…

OpenWRT配置SFTP远程文件传输,让数据分享更安全

文章目录 前言 1. openssh-sftp-server 安装2. 安装cpolar工具3.配置SFTP远程访问4.固定远程连接地址 前言 本次教程我们将在OpenWRT上安装SFTP服务&#xff0c;并结合cpolar内网穿透&#xff0c;创建安全隧道映射22端口&#xff0c;实现在公网环境下远程OpenWRT SFTP&#xf…

Python之函数进阶-函数执行原理

Python之函数进阶-函数执行原理 函数执行流程 C语言中&#xff0c;函数的活动和栈有关。栈是后进先出的数据结构。栈是由底端向顶端生长&#xff0c;栈顶加入数据成为压栈、入栈、栈顶弹出数据称为出栈。 def add(x, y):r x yprint(r)return rdef main():a 1r add(a, 2)r…

ubuntu上如何移植thttpd

thttpd的特点 thttpd 是一个简单、小巧、便携、快速且安全的 HTTP 服务器。 简单&#xff1a; 它只处理实现 HTTP/1.1 所需的最低限度。好吧&#xff0c;也许比最低限度多一点。 小&#xff1a; 请参阅比较图表。它还具有非常小的运行时大小&#xff0c;因为它不会分叉并且非…

牛客网刷题笔记131111 Python实现LRU+二叉树先中后序打印+SQL并列排序

从学校步入职场一年多&#xff0c;已经很久没刷过题了&#xff0c;为后续稍微做些提前的准备&#xff0c;还是重新开始刷刷题。 从未做过计划表&#xff0c;这回倒是做了个计划表&#xff0c;希望能坚持吧。 刷题比较随性且量级不大&#xff0c;今天就写了2个算法2个sql&#x…

无需公网IP,贝锐花生壳内网穿透远程访问NAS

群晖DSM 7.0及以上版本 1.1 安装运行花生壳套件 &#xff08;1&#xff09;通过浏览器输入群晖NAS的内网地址&#xff0c;登录进去后&#xff0c;点击【套件中心】&#xff0c;搜索【花生壳】&#xff0c;并点击【安装套件】&#xff1b; &#xff08;2&#xff09; 勾选我接…

Jvm虚拟机

问题&#xff1a; 计算机能识别的语言是二进制&#xff0c;Java文件是程序员编写的&#xff0c;如何能够在计算机上运行&#xff1f; 以及Java为什么可以实现跨平台&#xff1f; 一Java的jdk中有jvm虚拟机 可以将文件转换为字节码文件 使得它可以在各种平台上运行&#xff0c;这…

Openlayers:自定义Controls

Openlayers是一款优秀的二维开源地图框架,支持矢量/栅格图层,支持移动端,并且易于自定义和拓展。下面来讲述一下自定义Control的基本思路。 openlayers-features 问题描述 最近在做个人项目时,遇到了一个小问题,就是在地图中心添加一个十字针形状的符号,用来表示地图中心…

Java入门篇 之 补 类与对象

本篇碎碎念&#xff1a;每个人的想法都不一样&#xff0c;不要强求&#xff0c;顺其自然&#xff0c;要相信&#xff0c;一切都在向好的方面前进&#xff01;&#xff01;&#xff01;&#xff01; 今日份励志文案:山海的浩瀚&#xff0c;宇宙的浪漫&#xff0c;都在我内心翻腾…

LeetCode200.岛屿数量

看完题目我还感觉这道题目有点难&#xff0c;没想到20分钟不到就完全靠自己给写出来了。我就是按照自己的想法来&#xff0c;我用一个等大的visit数组来表示grid数组中的这个元素是否被访问过&#xff08;是否已经被判断了是不是岛屿&#xff09;。 先用一个大的循环对grid数组…