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 相关文章推荐
Jquery Ajax.ashx 高效分页实现代码
Oct 20 Javascript
JS Pro-深入面向对象的程序设计之继承的详解
May 07 Javascript
javascript仿php的print_r函数输出json数据
Sep 13 Javascript
JavaScript的jQuery库中ready方法的学习教程
Aug 14 Javascript
JavaScript中eval()函数用法详解
Dec 14 Javascript
快速解决js动态改变dom元素属性后页面及时渲染的问题
Jul 06 Javascript
微信JS-SDK选取手机照片上传功能
Apr 21 Javascript
js使用i18n实现页面国际化的方法
May 09 Javascript
vue移动端弹框组件的实例
Sep 25 Javascript
详解Vue 单文件组件的三种写法
Feb 19 Javascript
小程序实现背景音乐播放和暂停
Jun 19 Javascript
vue+openlayers绘制省市边界线
Dec 24 Vue.js
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编程最快明白》第八讲:php启发和小结
2010/11/01 PHP
使用PHP备份MySQL和网站发送到邮箱实例代码
2013/11/28 PHP
thinkPHP+PHPExcel实现读取文件日期的方法(含时分秒)
2016/07/07 PHP
CI(CodeIgniter)框架视图中加载视图的方法
2017/03/24 PHP
Thinkphp 框架扩展之Widget扩展实现方法分析
2020/04/23 PHP
JavaScript 字符串与数组转换函数[不用split与join]
2009/12/13 Javascript
用Jquery实现可编辑表格并用AJAX提交到服务器修改数据
2009/12/27 Javascript
js 数组的for循环到底应该怎么写?
2010/05/31 Javascript
Javascript 判断是否存在函数的方法
2013/01/03 Javascript
javascript模拟地球旋转效果代码实例
2013/12/02 Javascript
JS可以控制样式的名称写法一览
2014/01/16 Javascript
jquery用offset()方法获得元素的xy坐标
2014/09/06 Javascript
javascript 原型链维护和继承详解
2014/11/26 Javascript
jQuery实现有动画淡出效果的二级折叠菜单代码
2015/10/17 Javascript
js实现完美兼容各大浏览器的人民币大小写相互转换
2015/10/29 Javascript
TypeScript Type Innference(类型判断)
2016/03/10 Javascript
基于Bootstrap实现tab标签切换效果
2020/04/15 Javascript
jQuery无刷新上传之uploadify3.1简单使用
2016/06/18 Javascript
jQuery设置聚焦并使光标位置在文字最后的实现方法
2016/08/02 Javascript
jquery获取easyui日期控件的值实现方法
2016/11/09 Javascript
对Vue.js之事件的绑定(v-on: 或者 @ )详解
2018/09/15 Javascript
在vue中使用jsx语法的使用方法
2019/09/30 Javascript
python调用机器喇叭发出蜂鸣声(Beep)的方法
2015/03/23 Python
Python常用的文件及文件路径、目录操作方法汇总介绍
2015/05/21 Python
pytorch查看torch.Tensor和model是否在CUDA上的实例
2020/01/03 Python
基于Python实现体育彩票选号器功能代码实例
2020/09/16 Python
CSS3 实现穿梭星空动画
2020/11/13 HTML / CSS
数控专业应届生求职信
2013/11/27 职场文书
最新创业融资计划书
2014/01/19 职场文书
个人查摆剖析材料
2014/02/04 职场文书
经销商订货会主持词
2014/03/27 职场文书
加入学生会演讲稿
2014/04/24 职场文书
北京大学中文系教授推荐的10本小说
2019/08/08 职场文书
三年级作文之趣事作文
2019/11/04 职场文书
基于JavaScript实现省市联动效果
2021/06/22 Javascript
Python实战实现爬取天气数据并完成可视化分析详解
2022/06/16 Python