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 URL传中文参数引发的乱码问题
Sep 02 Javascript
js创建对象的几种常用方式小结(推荐)
Oct 24 Javascript
js 获取计算后的样式写法及注意事项
Feb 25 Javascript
使用jQuery动态加载js脚本文件的方法
Apr 03 Javascript
JavaScript极简入门教程(二):对象和函数
Oct 25 Javascript
深入理解JavaScript系列(17):面向对象编程之概论详细介绍
Mar 04 Javascript
JavaScript判断页面加载完之后再执行预定函数的技巧
May 17 Javascript
AngularJs 指令详解及示例代码
Sep 01 Javascript
React-Native实现ListView组件之上拉刷新实例(iOS和Android通用)
Jul 11 Javascript
动态创建Angular组件实现popup弹窗功能
Sep 15 Javascript
vue自定义switch开关组件,实现样式可自行更改
Nov 01 Javascript
vue点击页面空白处实现保存功能
Nov 06 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/07/01 PHP
PHP中实现接收多个name相同但Value不相同表单数据实例
2015/02/03 PHP
通过JAVAScript实现页面自适应
2007/01/19 Javascript
JavaScript入门教程(8) Location地址对象
2009/01/31 Javascript
实用的Jquery选项卡TAB示例代码
2013/08/28 Javascript
AngularJS快速入门
2015/04/02 Javascript
javascript鼠标滑动评分控件完整实例
2015/05/13 Javascript
JS实现自动固定顶部的悬浮菜单栏效果
2015/09/16 Javascript
喜大普奔!jQuery发布 3.0 最终版
2016/06/12 Javascript
AngularJs入门教程之环境搭建+创建应用示例
2016/11/01 Javascript
js实现表单提交后不重新刷新当前页面
2016/11/30 Javascript
详解VUE-地区选择器(V-Distpicker)组件使用心得
2018/05/07 Javascript
js实现简单掷骰子小游戏
2019/10/24 Javascript
如何在Express4.x中愉快地使用async的方法
2020/11/18 Javascript
微信小程序组件生命周期的踩坑记录
2021/03/03 Javascript
跟老齐学Python之网站的结构
2014/10/24 Python
Python基于smtplib实现异步发送邮件服务
2015/05/28 Python
Python数据拟合与广义线性回归算法学习
2017/12/22 Python
Python无损音乐搜索引擎实现代码
2018/02/02 Python
python中dir()与__dict__属性的区别浅析
2018/12/10 Python
Python直接赋值、浅拷贝与深度拷贝实例分析
2019/06/18 Python
基于python分析你的上网行为 看看你平时上网都在干嘛
2019/08/13 Python
基于Python中Remove函数的用法讨论
2020/12/11 Python
美国鲍勃商店:Bob’s Stores
2018/07/22 全球购物
澳大利亚最受欢迎的美发和美容在线商店:Catwalk
2018/12/12 全球购物
专科文秘应届生求职信
2013/11/18 职场文书
大学生旅游业创业计划书
2014/01/29 职场文书
学校三八妇女节活动情况总结
2014/03/09 职场文书
学习朴航瑛老师爱岗敬业先进事迹思想汇报
2014/09/17 职场文书
公路局群众路线教育实践活动第一阶段工作汇报
2014/10/25 职场文书
教师群众路线学习心得体会
2014/11/04 职场文书
新闻通讯稿模板
2015/07/22 职场文书
七年级英语教学反思
2016/02/15 职场文书
JS异步堆栈追踪之为什么await胜过Promise
2021/04/28 Javascript
日本官方排名前10的动漫,名侦探柯南上榜,第一是一部创造历史的动漫
2022/03/18 日漫
安装Ruby和 Rails的详细步骤
2022/04/19 Ruby