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 相关文章推荐
关于文本框的一些限制控制总结~~
Apr 15 Javascript
js使用eval解析json(js中使用json)
Jan 17 Javascript
JS根据年月获得当月天数的实现代码
Jul 03 Javascript
node.js中的emitter.emit方法使用说明
Dec 10 Javascript
js使用onmousemove和onmouseout获取鼠标坐标的方法
Mar 31 Javascript
JQuery CheckBox(复选框)操作方法汇总
Apr 15 Javascript
AngularJS使用angular-formly进行表单验证
Dec 27 Javascript
Angular.js如何从PHP读取后台数据
Mar 24 Javascript
Javascript之Math对象详解
Jun 07 Javascript
JS中innerHTML和pasteHTML的区别实例分析
Jun 22 Javascript
微信小程序开发的四十个技术窍门总结(推荐)
Jan 23 Javascript
mpvue 项目初始化及实现授权登录的实现方法
Jul 20 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/12/02 PHP
PHP_NETWORK_GETADDRESSES: GETADDRINFO FAILED问题解决办法
2014/05/04 PHP
php三元运算符知识汇总
2015/07/02 PHP
php实现处理输入转义字符的代码
2015/11/08 PHP
CodeIgniter自定义控制器MY_Controller用法分析
2016/01/20 PHP
论坛特效代码收集(落伍转发-不错)
2006/12/02 Javascript
JQuery AJAX实现目录浏览与编辑的代码
2008/10/21 Javascript
网页和浏览器兼容性问题汇总(draft1)
2009/06/01 Javascript
jQuery Clone Bug解决代码
2010/12/22 Javascript
JS打开层/关闭层/移动层动画效果的实例代码
2013/05/11 Javascript
jQuery实现随意改变div任意属性的名称和值(部分原生js实现)
2013/05/28 Javascript
js换图片效果可进行定时操作
2014/06/09 Javascript
JQuery中属性过滤选择器用法实例分析
2015/05/18 Javascript
跟我学习javascript解决异步编程异常方案
2015/11/23 Javascript
JavaScript“尽快失败”的原则实例详解
2016/10/08 Javascript
Bootstrap3 datetimepicker控件使用实例
2016/12/13 Javascript
基于JavaScript实现全选、不选和反选效果
2017/02/15 Javascript
discuz表情的JS提取方法分析
2017/03/22 Javascript
js弹性势能动画之抛物线运动实例详解
2017/07/27 Javascript
React+react-dropzone+node.js实现图片上传的示例代码
2017/08/23 Javascript
解决Layui中layer报错的问题
2019/09/03 Javascript
node.js express捕获全局异常的三种方法实例分析
2019/12/27 Javascript
Python生成pdf文件的方法
2014/08/04 Python
Python类的专用方法实例分析
2015/01/09 Python
python删除指定类型(或非指定)的文件实例详解
2015/07/06 Python
Python随机生成带特殊字符的密码
2016/03/02 Python
win10环境下python3.5安装步骤图文教程
2017/02/03 Python
详解Python3.6安装psutil模块和功能简介
2018/05/30 Python
python3读取excel文件只提取某些行某些列的值方法
2018/07/10 Python
十个Python练手的实战项目,学会这些Python就基本没问题了(推荐)
2019/04/26 Python
python 缺失值处理的方法(Imputation)
2019/07/02 Python
用Pelican搭建一个极简静态博客系统过程解析
2019/08/22 Python
Python3操作MongoDB增册改查等方法详解
2020/02/10 Python
Topman美国官网:英国著名的国际平价时尚男装品牌
2017/12/22 全球购物
美国50岁以上单身人士约会平台:SilverSingles
2018/06/29 全球购物
瑞士隐形眼镜和护理产品网上商店:Linsenklick
2019/10/21 全球购物