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 相关文章推荐
JavaScript 比较时间大小的代码
Apr 24 Javascript
六款帮助你实现惊艳视差滚动效果的jQuery插件
Sep 14 Javascript
从QQ网站中提取的纯JS省市区三级联动菜单
Dec 25 Javascript
jQuery 鼠标经过(hover)事件的延时处理示例
Apr 14 Javascript
jquery form表单获取内容以及绑定数据
Feb 24 Javascript
Bootstrap模态框禁用空白处点击关闭
Oct 20 Javascript
详解基于javascript实现的苹果系统底部菜单
Dec 02 Javascript
Vue2.0实现1.0的搜索过滤器功能实例代码
Mar 20 Javascript
Node.js readline模块与util模块的使用
Mar 01 Javascript
jQuery实现点击自身以外区域关闭弹出层功能完整示例【改进版】
Jul 31 jQuery
JavaScript之解构赋值的理解
Jan 30 Javascript
Vue data的数据响应式到底是如何实现的
Feb 11 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加密解密实用类分享
2014/01/07 PHP
兼容ie6浏览器的php下载文件代码分享
2014/07/14 PHP
PHP 中 Orientation 属性判断上传图片是否需要旋转
2015/10/16 PHP
phalcon框架使用指南
2016/02/23 PHP
laravel 5异常错误:FatalErrorException in Handler.php line 38的解决
2017/10/12 PHP
Yii2.0建立公共方法简单示例
2019/01/29 PHP
php 利用socket发送GET,POST请求的实例代码
2020/07/04 PHP
BOOM vs RR BO5 第三场 2.14
2021/03/10 DOTA
发布一个高效的JavaScript分析、压缩工具 JavaScript Analyser
2007/11/30 Javascript
用JS剩余字数计算的代码
2008/07/03 Javascript
JavaScript 高级篇之闭包、模拟类,继承(五)
2012/04/07 Javascript
在jQuery中 常用的选择器介绍
2013/04/16 Javascript
jQuery图片的展开和收缩实现代码
2013/04/16 Javascript
form.submit()不能提交表单的原因分析
2014/10/23 Javascript
javascript数组随机排序实例分析
2015/07/22 Javascript
JavaScript setTimeout使用闭包功能实现定时打印数值
2015/12/18 Javascript
Jqprint实现页面打印
2017/01/06 Javascript
JavaScript利用Date实现简单的倒计时实例
2017/01/12 Javascript
Bootstrap treeview实现动态加载数据并添加快捷搜索功能
2018/01/07 Javascript
Vue-cli Eslint在vscode里代码自动格式化的方法
2018/02/23 Javascript
vue如何将v-for中的表格导出来
2018/05/07 Javascript
[01:08:17]2018DOTA2亚洲邀请赛3月29日 小组赛B组 EG VS VGJ.T
2018/03/30 DOTA
python自动发邮件库yagmail的示例代码
2018/02/23 Python
如何利用python查找电脑文件
2018/04/27 Python
python使用布隆过滤器的实现示例
2020/08/20 Python
简单了解Python字典copy与赋值的区别
2020/09/16 Python
如何避免常见的6种HTML5错误用法
2017/11/06 HTML / CSS
JustFab加拿大:女鞋、靴子、手袋和服装在线
2018/05/18 全球购物
俄罗斯最大的隐形眼镜销售网站:Ochkov.Net
2021/02/07 全球购物
SQL Server 2000数据库的文件有哪些,分别进行描述。
2015/11/09 面试题
手机银行营销方案
2014/03/14 职场文书
2015秋学期开学寄语
2015/05/28 职场文书
MySQL 分页查询的优化技巧
2021/05/12 MySQL
python批量创建变量并赋值操作
2021/06/03 Python
详解JAVA中的OPTIONAL
2021/06/14 Java/Android
Java对文件的读写操作方法
2022/04/29 Java/Android