Vue.js之slot深度复制详解


Posted in Javascript onMarch 10, 2017

前言

在Vue中,slot是一个很有用的特性,可以用来向组件内部插入一些内容。slot就是“插槽”的意思,用大白话说就是:定义组件的时候留几个口子,由用户来决定插入的内容。

例如我们定义一个组件MyComponent,其包含一个slot:

Vue.component('MyComponent', {
 template: `
 <div>
  <slot></slot>
 </div>
 `
})

当调用<MyComponent>123</MyComponent>时,会渲染为如下DOM结构:

<div>
 123
</div>

现在又有新需求了,我们希望调用<MyComponent>123</MyComponent>时,渲染出这样的DOM结构:

<div>
 123
 123
</div>

看起来很容易实现,即再为MyComponent添加一个slot:

Vue.component('MyComponent', {
 template: `
 <div>
  <slot></slot>
  <slot></slot>
 </div>
 `
})

渲染出的结构也确实如你所愿,唯一美中不足的是控制台有一个小小的Warning:

Duplicate presence of slot "default" found in the same render tree

如果你不是强迫症患者,这时候你可以收工安心回家睡觉了。直到有一天你的同事向你抱怨,为什么向MyComponent插入一个自定义组件会渲染不出来?

例如有一自定义组件MyComponent2:

Vue.component('MyComponent2', {
 template: `
 <div>456</div>
 `
})

当调用<MyComponent><MyComponent2></MyComponent2></MyComponent>时,预期渲染为如下DOM结构:

<div>
 <div>456</div>
 <div>456</div>
</div>

为什么不能正常工作呢?估计是前面的那个Warning搞得鬼,通过查询发现在Vue 2.0中不允许有重名的slot:

重名的 Slots 移除

同一模板中的重名 已经弃用。当一个 slot 已经被渲染过了,那么就不能在同一模板其它地方被再次渲染了。如果要在不同位置渲染同一内容,可一用 prop 来传递。

文档中提示可以用props来实现,然而在我的用例中显然是不合适的。经过搜索后,最靠谱的方法是手写render函数,将slot中的内容复制到其他的位置。

将之前的MyComponent改为render函数的方式定义:

Vue.component('MyComponent', {
 render (createElement) {
 return createElement('div', [
  ...this.$slots.default,
  ...this.$slots.default
 ])
 }
})

在上面的定义中我们插入了两个this.$slots.default,测试下能不能正常工作。然而并没有什么卵用,Vue文档在render函数这一章有以下说明:

VNodes 必须唯一

所有组件树中的 VNodes 必须唯一

这意味着我们不能简单地在不同位置引用this.$slots.default,必须对slot进行深度复制。深度复制的函数如下:

function deepClone(vnodes, createElement) {
 function cloneVNode (vnode) {
 const clonedChildren = vnode.children && vnode.children.map(vnode => cloneVNode(vnode));
 const cloned = createElement(vnode.tag, vnode.data, clonedChildren);
 cloned.text = vnode.text;
 cloned.isComment = vnode.isComment;
 cloned.componentOptions = vnode.componentOptions;
 cloned.elm = vnode.elm;
 cloned.context = vnode.context;
 cloned.ns = vnode.ns;
 cloned.isStatic = vnode.isStatic;
 cloned.key = vnode.key;
 return cloned;
 }
 const clonedVNodes = vnodes.map(vnode => cloneVNode(vnode))
 return clonedVNodes;
}

上面的核心函数就是cloneVNode() ,它递归地创建VNode,实现深度复制。VNode的属性很多,我并不了解哪些是关键属性,只是参照着Vue的源码一并地复制过来。

基于以上函数,我们更改MyComponent的定义:

Vue.component('MyComponent', {
 render (createElement) {
 return createElement('div', [
  ...this.$slots.default,
  ...deepClone(this.$slots.default, createElement)
 ])
 }
})

经测试,一切正常。

总结

在Vue 1.0中重名的slots并不会出现什么问题,不知道为什么在2.0中取消了这个功能。我听说React提供了复制Element的标准函数,希望Vue也能提供这个函数,免得大家踩坑。以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

Javascript 相关文章推荐
一个简单的js渐显(fadeIn)渐隐(fadeOut)类
Jun 19 Javascript
jQuery Jcrop插件实现图片选取功能
Nov 23 Javascript
动态的改变IFrame的高度实现IFrame自动伸展适应高度
Dec 28 Javascript
浅析js设置控件的readonly与enabled属性问题
Dec 25 Javascript
jquery创建表格(自动增加表格)代码分享
Dec 25 Javascript
javascript文件中引用依赖的js文件的方法
Mar 17 Javascript
javascript实现checkbox复选框实例代码
Jan 10 Javascript
实现React单页应用的方法详解
Aug 02 Javascript
Jquery遍历select option和添加移除option的实现方法
Aug 26 Javascript
Validform表单验证总结篇
Oct 31 Javascript
JS实现汉字与Unicode码相互转换的方法详解
Apr 28 Javascript
js实现表单项的全选、反选及删除操作示例
Jun 05 Javascript
JS实现的自动打字效果示例
Mar 10 #Javascript
jquery实现的table排序功能示例
Mar 10 #Javascript
微信小程序 向左滑动删除功能的实现
Mar 10 #Javascript
常用的js方法合集
Mar 10 #Javascript
利用Angular+Angular-Ui实现分页(代码加简单)
Mar 10 #Javascript
JS中利用localStorage防止页面动态添加数据刷新后数据丢失
Mar 10 #Javascript
C#微信小程序服务端获取用户解密信息实例代码
Mar 10 #Javascript
You might like
用libtemplate实现静态网页生成
2006/10/09 PHP
PHP获取当前文件所在目录 getcwd()函数
2009/05/13 PHP
认识并使用PHP超级全局变量
2010/01/26 PHP
php获取当前网址url并替换参数或网址的方法
2010/06/06 PHP
PHP 作用域解析运算符(::)
2010/07/27 PHP
php curl 登录163邮箱并抓取邮箱好友列表的代码(经测试)
2011/04/07 PHP
jMessageBox 基于jQuery的窗口插件
2009/12/09 Javascript
JQuery1.6 使用方法三
2011/11/23 Javascript
jquery事件与函数的使用介绍
2013/09/29 Javascript
我的Node.js学习之路(三)--node.js作用、回调、同步和异步代码 以及事件循环
2014/07/06 Javascript
javascript时间排序算法实现活动秒杀倒计时效果
2021/01/28 Javascript
Nodejs Stream 数据流使用手册
2016/04/17 NodeJs
jQuery实现下拉菜单(内容为时间)的实时更新及图表的随动更新的方法
2016/07/07 Javascript
Three.js学习之几何形状
2016/08/01 Javascript
JavaScript 格式化数字、金额、千分位、保留几位小数、舍入舍去
2019/07/23 Javascript
Vue中使用Lodop插件实现打印功能的简单方法
2019/12/19 Javascript
[02:47]3.19DOTA2发布会 国服成长历程回顾
2014/03/25 DOTA
利用pyinstaller将py文件打包为exe的方法
2018/05/14 Python
Python小游戏之300行代码实现俄罗斯方块
2019/01/04 Python
Windows10+anacond+GPU+pytorch安装详细过程
2020/03/24 Python
Python Django form 组件动态从数据库取choices数据实例
2020/05/19 Python
python获得命令行输入的参数的两种方式
2020/11/02 Python
PyChon中关于Jekins的详细安装(推荐)
2020/12/28 Python
HTML5 Canvas的事件处理介绍
2015/04/24 HTML / CSS
成人大专生实习期的自我评价
2013/10/02 职场文书
大专生简历的自我评价
2013/11/26 职场文书
人力资源专员岗位职责
2014/01/30 职场文书
抽样调查项目计划书
2014/04/24 职场文书
水利水电专业自荐信
2014/07/08 职场文书
2014年“世界无车日”活动方案
2014/09/21 职场文书
反四风个人对照检查材料思想汇报
2014/09/25 职场文书
职称评定个人总结
2015/03/05 职场文书
销售区域经理岗位职责
2015/04/10 职场文书
学习十八大的感悟
2015/08/11 职场文书
2016母亲节感恩话语
2015/12/09 职场文书
Win11 Build 22000.829更新补丁KB5015882发布(附更新修复内容汇总)
2022/07/15 数码科技