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 相关文章推荐
写的htc的数据表格
Jan 20 Javascript
解javascript 混淆加密收藏
Jan 16 Javascript
javascript json2 使用方法
Mar 16 Javascript
js获取TreeView控件选中节点的Text和Value值的方法
Nov 24 Javascript
有关于eclipse配置spket需要注意的一些地方
Apr 07 Javascript
jquery中 $.expr使用实例介绍
Jun 09 Javascript
推荐JavaScript实现继承的最佳方式
Nov 11 Javascript
jQuery插件Validate实现自定义校验结果样式
Jan 18 Javascript
JS基于ocanvas插件实现的简单画板效果代码(附demo源码下载)
Apr 05 Javascript
JavaScript手机振动API
Jun 11 Javascript
AngularJS基础 ng-click 指令示例代码
Aug 01 Javascript
Vue实现多标签选择器
Nov 28 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
改写函数实现PHP二维/三维数组转字符串
2013/09/13 PHP
常用PHP数组排序函数归纳
2016/08/08 PHP
jQuery ajax+PHP实现的级联下拉列表框功能示例
2019/02/12 PHP
Laravel框架自定义公共函数的引入操作示例
2019/04/16 PHP
JAVASCRIPT keycode总结
2009/02/04 Javascript
Jquery 实现Tab效果 思路是js思路
2010/03/02 Javascript
原创javascript小游戏实现代码
2010/08/19 Javascript
禁用键盘上的(全局)指定键兼容iE、Chrome、火狐
2013/05/14 Javascript
Jquery选择子控件&quot;大于号&quot;和&quot; &quot;区别介绍及使用示例
2013/06/25 Javascript
js中的前绑定和后绑定详解
2013/08/01 Javascript
Iframe实现跨浏览器自适应高度解决方法
2014/09/02 Javascript
纯javascript实现自动发送邮件
2015/10/21 Javascript
jQuery模仿阿里云购买服务器选择购买时间长度的代码
2016/04/29 Javascript
js基本算法:冒泡排序,二分查找的简单实例
2016/10/08 Javascript
canvas实现图像截取功能
2017/02/06 Javascript
Webpack打包慢问题的完美解决方法
2017/03/16 Javascript
jQuery 防止相同的事件快速重复触发方法
2018/02/08 jQuery
JavaScript使用类似break机制中断forEach循环的方法
2018/11/13 Javascript
原生JavaScript写出Tabs标签页的实例代码
2020/07/20 Javascript
VSCode 添加自定义注释的方法(附带红色警戒经典注释风格)
2020/08/27 Javascript
vue watch监控对象的简单方法示例
2021/01/07 Vue.js
[58:21]DOTA2亚洲邀请赛 4.3 突围赛 Liquid vs VGJ.T 第二场
2018/04/04 DOTA
Python 抓取动态网页内容方案详解
2014/12/25 Python
Python装饰器使用实例:验证参数合法性
2015/06/24 Python
Django验证码的生成与使用示例
2017/05/20 Python
Python高级特性切片(Slice)操作详解
2018/09/27 Python
浅谈python的深浅拷贝以及fromkeys的用法
2019/03/08 Python
Python requests设置代理的方法步骤
2020/02/23 Python
详解修改Anaconda中的Jupyter Notebook默认工作路径的三种方式
2021/01/24 Python
CSS中的字体大小设置属性总结
2016/05/24 HTML / CSS
EGO Shoes美国/加拿大:英国时髦鞋类品牌
2018/08/04 全球购物
企业员工培训感言
2014/02/26 职场文书
交通违章检讨书
2014/09/21 职场文书
结婚保证书(卖身契)
2015/02/26 职场文书
Python OpenCV 图像平移的实现示例
2021/06/04 Python
斗罗大陆八大特殊魂兽,龙族始祖排榜首,第五最残忍(翠魔鸟)
2022/03/18 国漫