热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

vue怎么销毁dom_Vue中如何转移Dom?

这个标题可能很拗口,请允许我先做下简短的解释。在我们使用Vue进行开发时,Dom最终渲染的结构与template嵌套逻辑是一致的,有着严格

这个标题可能很拗口,请允许我先做下简短的解释。

在我们使用 Vue 进行开发时,Dom 最终渲染的结构与 template 嵌套逻辑是一致的,有着严格的父子级关系。

假如我们有特殊需求,需要将某个 viewModel 的 Dom 渲染到父级之外,需要如何操作?

一、真的有这种需求 ?

可能你在看了前面介绍之后会觉得很奇怪,也意识到自己的开发经验里从来没有过这样的需求。

什么样的场景才会需要转移 Dom 呢 ?

如果你有过 UI 组件开发的经验,可能会发现,随着模块划分越来越细,模块层级越来越深,某些组件的 Dom 并不适合渲染在自身所属的 Dom 上。

比如常见的返回顶部按钮,悬浮广告,还有下面三个更生动的例子。

对话框

右键菜单

用户提示组件

418216adbb33ea53def23d065e544719.png

花十秒钟想一下这些组件有什么样的特点 ?

开始

10

9

8

7

6

5

4

3

2

1

结束

这些模块都属于用户交互组件,并不属于页面静态布局的一部分,需要用户的操作来唤醒。

并且受限于页面各个层级的 overflow 设置,如果嵌入结构过深很容易导致渲染不完整。z-index 管理也会更加混乱,并且高频的创建、销毁也会引起不必要的局部重绘。

因此在开发 UI 用户交互组件的时候,如果有必要,可以考虑将 Dom 转移到组件外部,可能更便于管理。

二、遇到了什么蛋疼的功能?

之所以会思考这个问题,是因为最近小剧所在的团队正在开发一款新产品, 暂时不方便透露具体的产品名,等到上线后再找机会和你聊一聊。

因为产品的交互特性需要「右键菜单」的功能,按照惯例开始在 Npm 上找前人发布的开源包。可能在 API 支持程度、组件初始化方式上都和预期差异较大,所以便决定了自己开发一款右键菜单组件。

还好小剧曾在 jQuery 时代写过一款右键菜单组件,所以主体逻辑并没有花太多时间。

结合前面提到的用户交互组件的特点,为了保证右键菜单不受各层级 overflow 的限制,同时便于管理 z-index 层级,需要将右键菜单的 Dom 脱离触发元素层级。

正是因为开发这个功能,做了相关代码对比之后,小剧才总结了这篇文章。

三、如何转移 Dom ?

经过自己实践和对前人代码的研究,发现大概有两种方案可以实现 Dom 的转移,分别是:

渲染前转移 vnode

渲染后转移 $el 节点

渲染前转移 vnode 有很多应用方式,小剧这里以 new Vue 创建新节点为例说明。

渲染后转移 $el 节点相对比较容易理解,下面会举两个例子来说明。

3.1、借助 new Vue 创建新节点

这里是小剧采用的方案,是渲染前转移 vnode 的一种,实现逻辑比较笨。抛开右键菜单的具体逻辑,单纯来聊一下如何通过 new Vue 来转移 Dom。

先来上两段代码,后面慢慢介绍。

代码一:定义核心模块

// 提供给用户使用的菜单定义组件

Vue.component('contextmenu', {

name: 'contextmenu-collect',

render () {

return null

}

})

// 定义指令

Vue.directive('menu', {

inserted (el, binding, vnode) {

let vm = vnode.context

let refKey = binding.arg

// 监听右键菜单事件

el.addEventListener('contextmenu', event => {

createMenu(event, vm.$refs[refKey].$slots.default)

event.preventDefault()

})

}

})

// 创建菜单

function createMenu (event, vnode){

let newNode = document.createElement('div')

document.body.appendChild(newNode)

new Vue({

el: newNode,

render(createElement) {

createElement(

'div',

{},

vnode

)

}

})

}

代码二:用户侧使用

右键点我吧

我是菜单里的元素

我也是菜单里的元素耶~

我们来看最简单的模块:contextmenu,它不处理任何逻辑,甚至从 render 函数可以看出来,它不渲染任何 Dom 节点。

再看看 menu 指令,它是串联起全部功能最核心的部分,通过监听元素右键菜单事件,阻止浏览器默认事件并且根据用户定义的菜单内容创建模拟菜单。

createMenu 方法内部,会默认创建新的节点放在 body 节点下,并通过 new Vue 将 vnode 转移到了外侧,也就起到了 Dom 转移的作用。

小思考

contentmenu 模块的 render 方法什么都不渲染,为什么还能找到 vnode ?

3.2、手动 appendChild 方式

不知道你有没有用过 Element UI 框架。她的 Dialog 对话框有一个叫 append-to-body 的 prop 参数,只要你添加了这个参数,对话框就会被渲染到页面的 body 节点下。

ff2e8e7f29439b361fac29650adf166b.png

这一个简单的参数可以解决弹窗嵌套等一系列问题,我们来看一下它是怎么实现的。

mounted() {

if (this.visible) {

this.rendered = true;

this.open();

if (this.appendToBody) {

document.body.appendChild(this.$el);

}

}

},

destroyed() {

// if appendToBody is true, remove DOM node after destroy

if (this.appendToBody && this.$el && this.$el.parentNode) {

this.$el.parentNode.removeChild(this.$el);

}

}

相比之下 Element 在对 Dialog 的 Dom 转移实现上更为简单。

在 mounted 阶段 Dom 已经渲染完毕,根据条件将自身的 Dom 强行移至 body 内部,在模块销毁时再手动移除 Dom 节点。

通过这一前一后两个生命周期钩子的操作就完成了 Dom 的转移。

小提示

appendChild 是原生 Dom 操作方法,插入节点时会同时从源 Dom 节点移除,不会创造出新节点。

3.3、指令封装模式

非常感谢胖坤的提醒,让我发现了这个有意思的 Dom 转移方法。

指令封装本质上也是 appendChild 的模式,只是将 Dom 转移操作提炼成指令,需要的时候直接调用即可完成。

这里就不展开具体代码了,我们聊一聊小剧在研究指令封装模式时发现的有意思的事情。

提示:开始讲故事了

在小剧将右键菜单功能写完之后,得意洋洋地在胖坤面前炫耀流弊的 Dom 转移的实现方式。

胖坤轻描淡写了一句: iview 的源码里也有 Dom 转移的方法,你可以看一下。

内心受挫后,小剧找到了 iview 的源码,顺着代码找到了下面的文件,逻辑相当简练,而且没有第三方依赖。

前人总结的代码就是干脆利落,而且还考虑到了将 Dom 转移到指定节点的需求。

正当小剧准备以备后用的时候,竟然发现文件顶上有两行注释。

// Thanks to: https://github.com/airyland/vux/blob/v2/src/directives/transfer-dom/index.js

// Thanks to: https://github.com/calebroseland/vue-dom-portal

打开第一行的链接后印入眼帘的又是一行注释:

// Thanks to: https://github.com/calebroseland/vue-dom-portal

简直是俄罗斯套娃般的操作。

这三份代码虽然在命名上,部分细节上不太一样,但总体思路和实现方式惊人的一致。

从 thanks to 的注释可以看出来,代码拷贝路径非常清晰,iview 借鉴了 vux 的 transfer-dom 实现方式,而 vux 的 transfer-dom 是基于 vue-dom-portal 改造而来的。

也就是说,这段代码的根源在 vue-dom-portal 仓库内。

那么问题来了:

既然根源在 vue-dom-portal,为什么借鉴的小伙伴要把它改名成 transfer-dom 呢?

很快,这个疑问在 vue-dom-portal 的文档里找到了答案。

Similar to vue-transfer-dom, but updated for vue@2.x.

因为在 vue@1.x 的时候,有另外一个人写了一个叫 vue-transfer-dom 的指令,并且发布至 Npm。

但是原作者并没有开发支持 vue@2.x 的版本,为了有所区分,新的代码取名为 vue-dom-portal。

而 vux、iview 都是从 vue@1.x 升级而来的,因此延续了老的命名,但是却使用了新的代码。

提示:故事讲完了

刚才的故事里一共提到了四个仓库。

其中最早的 vue-transfer-dom 版本较老,已经不能使用了。

另外两个加了 thanks to 的代码仅仅是代码片段,为了防止俄罗斯套娃不建议你再加个 thanks to 拷贝走。

因此如果你想用指令封装的模式转移 Dom,可以直接使用 vue-dom-portal。

四、项目里建议使用 Dom 转移么?

Use at your own risk! No tests have been written, but it seems to be working.

上面这段话摘自vue-dom-portal 的文档。

无论你使用前面提到的哪种方法来完成 Dom 的转移,都是和 Vue 模块化设计初衷不符的。

你需要自己来保证逻辑的完整性,并且避免因为数据共享导致的内存泄漏。

因此常规的项目开发是不建议手动转移 Dom 的,除非你遇到了类似本文中提到的这些场景。

希望这篇文章有坑到你。



推荐阅读
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 标题: ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • 使用nodejs爬取b站番剧数据,计算最佳追番推荐
    本文介绍了如何使用nodejs爬取b站番剧数据,并通过计算得出最佳追番推荐。通过调用相关接口获取番剧数据和评分数据,以及使用相应的算法进行计算。该方法可以帮助用户找到适合自己的番剧进行观看。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 本文介绍了解决IE678伪类不兼容问题的方法,包括少用CSS3和HTML5独有的属性,使用CSS hacker,使用last-child清除浮动、批量添加标签、去掉list item最后一个的border-right等技巧。同时还介绍了使用after清除浮动时加上IE独有属性zoom:1的处理方法。另外,本文还提到可以使用jQuery代替批量添加标签的功能,以及使用负边距和CSS2选择器element+element去掉list item最后一个的border-right的方法。 ... [详细]
  • 本文介绍了如何使用jQuery和AJAX来实现动态更新两个div的方法。通过调用PHP文件并返回JSON字符串,可以将不同的文本分别插入到两个div中,从而实现页面的动态更新。 ... [详细]
  • Android实战——jsoup实现网络爬虫,糗事百科项目的起步
    本文介绍了Android实战中使用jsoup实现网络爬虫的方法,以糗事百科项目为例。对于初学者来说,数据源的缺乏是做项目的最大烦恼之一。本文讲述了如何使用网络爬虫获取数据,并以糗事百科作为练手项目。同时,提到了使用jsoup需要结合前端基础知识,以及如果学过JS的话可以更轻松地使用该框架。 ... [详细]
  • Node.js学习笔记(一)package.json及cnpm
    本文介绍了Node.js中包的概念,以及如何使用包来统一管理具有相互依赖关系的模块。同时还介绍了NPM(Node Package Manager)的基本介绍和使用方法,以及如何通过NPM下载第三方模块。 ... [详细]
  • 本文介绍了使用jQuery实现图片预加载和等比例缩放的方法,同时提供了演示和相关代码。该方法可以重置图片的宽度和高度,并使图片在水平和垂直方向上居中显示。 ... [详细]
  • 获取ul中第一个li元素的五种方法和多个ul中第一个li元素的四种方法
    本文介绍了获取ul中第一个li元素的五种方法和多个ul中第一个li元素的四种方法,包括使用jQuery的选择器和遍历方法。通过这些方法,可以方便地获取到所需的元素,并进行相应的操作。 ... [详细]
author-avatar
真真贱贱_474
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有