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滚动条回到顶部的代码
Dec 06 Javascript
JS 获取浏览器和屏幕宽高等信息的实现思路及代码
Jul 31 Javascript
CheckBoxList多选样式jquery、C#获取选择项
Sep 06 Javascript
用Javascript获取页面元素的具体位置
Dec 09 Javascript
JavaScript中使用ActiveXObject操作本地文件夹的方法
Mar 28 Javascript
深入理解JavaScript系列(28):设计模式之工厂模式详解
Mar 03 Javascript
通用javascript代码判断版本号是否在版本范围之间
Nov 29 Javascript
详解angularjs中如何实现控制器和指令之间交互
May 31 Javascript
解决vue 中 echart 在子组件中只显示一次的问题
Aug 07 Javascript
angular5 子组件监听父组件传入值的变化方法
Sep 30 Javascript
JS实现盒子拖拽效果
Feb 06 Javascript
详解钉钉小程序组件之自定义模态框(弹窗封装实现)
Mar 07 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 冒泡排序算法的实现代码
2010/08/08 PHP
解析php常用image图像函数集
2013/06/24 PHP
PHP中使用GD库绘制折线图 折线统计图的绘制方法
2015/11/09 PHP
浅谈PHP命令执行php文件需要注意的问题
2016/12/16 PHP
ThinkPHP5框架中使用JWT的方法示例
2020/06/03 PHP
jQuery中多个元素的Hover事件解决方案
2014/06/12 Javascript
JavaScript学习笔记之数组求和方法
2016/03/23 Javascript
Bootstrap打造一个左侧折叠菜单的系统模板(一)
2016/05/17 Javascript
AngularJS实现数据列表的增加、删除和上移下移等功能实例
2016/09/05 Javascript
浅析JavaScript中break、continue和return的区别
2016/11/30 Javascript
jstree的简单实例
2016/12/01 Javascript
ES6概念 ymbol.for()方法
2016/12/25 Javascript
js实现textarea限制输入字数
2017/02/13 Javascript
JS实现禁止用户使用Ctrl+鼠标滚轮缩放网页的方法
2017/04/28 Javascript
元素全屏的设置与监听实例
2017/11/28 Javascript
使用Vue.js和Element-UI做一个简单登录页面的实例
2018/02/23 Javascript
vue实现个人信息查看和密码修改功能
2018/05/06 Javascript
vue使用中的内存泄漏【推荐】
2018/07/10 Javascript
vue实现虚拟列表功能的代码
2020/07/28 Javascript
Python将一个CSV文件里的数据追加到另一个CSV文件的方法
2018/07/04 Python
OpenCV+Python识别车牌和字符分割的实现
2019/01/31 Python
python中嵌套函数的实操步骤
2019/02/27 Python
python文件读取失败怎么处理
2020/06/23 Python
python利用google翻译方法实例(翻译字幕文件)
2020/09/21 Python
如何使用 Flask 做一个评论系统
2020/11/27 Python
详解Html5 监听拦截Android返回键方法
2018/04/18 HTML / CSS
长曲棍球装备:Lacrosse Monkey
2020/12/02 全球购物
《桃花心木》教学反思
2014/02/17 职场文书
中层干部考核评语
2015/01/04 职场文书
诚信承诺书
2015/01/19 职场文书
小学英语教学反思范文
2016/02/15 职场文书
Python中json.dumps()函数的使用解析
2021/05/17 Python
MySQL中的引号和反引号的区别与用法详解
2021/10/24 MySQL
分享CSS盒子模型隐藏的几种方式
2022/02/28 HTML / CSS
Python获取字典中某个key的value
2022/04/13 Python
Mysql查询时间区间日期列表,不会由于数据表数据影响
2022/04/19 MySQL