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 相关文章推荐
文本框获得焦点和失去焦点的判断代码
Mar 18 Javascript
html5的自定义data-*属性和jquery的data()方法的使用示例
Aug 21 Javascript
如何创建一个JavaScript弹出DIV窗口层的效果
Sep 25 Javascript
用Js实现的动态增加表格示例自己写的
Oct 21 Javascript
JavaScript前补零操作实例
Mar 11 Javascript
js基于setTimeout与setInterval实现多线程
Jun 17 Javascript
JS中Swiper的使用和轮播图效果
Aug 11 Javascript
JavaScript实现计算多边形质心的方法示例
Jan 31 Javascript
Vue 菜单栏点击切换单个class(高亮)的方法
Aug 22 Javascript
JS使用数组实现的队列功能示例
Mar 04 Javascript
简单了解Ajax表单序列化的实现方法
Jun 14 Javascript
jQuery三组基本动画与自定义动画操作实例总结
May 09 jQuery
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 JSON中文乱码的解决方法详解
2013/06/06 PHP
浅析php与数据库代码开发规范
2013/08/08 PHP
Linux系统中为php添加pcntl扩展
2016/08/28 PHP
ie 处理 gif动画 的onload 事件的一个 bug
2007/04/12 Javascript
javascript 一个函数对同一元素的多个事件响应
2009/07/25 Javascript
Node.js 学习笔记之简介、安装及配置
2015/03/03 Javascript
解决JS请求服务器gbk文件乱码的问题
2015/10/16 Javascript
js和jquery实现监听键盘事件示例代码
2020/06/24 Javascript
理解JavaScript表单的基础知识
2016/01/25 Javascript
JavaScript模拟鼠标右键菜单效果
2020/12/08 Javascript
AngularJS实现数据列表的增加、删除和上移下移等功能实例
2016/09/05 Javascript
JS实现两周内自动登录功能
2017/03/23 Javascript
一个可复用的vue分页组件
2017/05/15 Javascript
JS计算距当前时间的时间差实例
2017/12/29 Javascript
jQuery实现动态加载select下拉列表项功能示例
2018/05/31 jQuery
JS实现深度优先搜索求解两点间最短路径
2019/01/17 Javascript
详解nodejs 开发企业微信第三方应用入门教程
2019/03/12 NodeJs
简单了解小程序+node梳理登陆流程
2019/06/24 Javascript
JS模拟实现京东快递单号查询
2020/11/30 Javascript
[00:43]魔廷新尊——痛苦女王至宝捆绑包
2020/06/12 DOTA
决策树的python实现方法
2014/11/18 Python
python爬取网页内容转换为PDF文件
2020/07/28 Python
Selenium元素的常用操作方法分析
2018/08/10 Python
python中将正则过滤的内容输出写入到文件中的实例
2018/10/21 Python
详解PyCharm安装MicroPython插件的教程
2019/06/24 Python
Python爬虫如何应对Cloudflare邮箱加密
2020/06/24 Python
芭比波朗加拿大官方网站:Bobbi Brown Cosmetics CA
2020/11/05 全球购物
EJB面试题
2015/07/28 面试题
商务考察邀请函范文
2014/01/21 职场文书
大学毕业生求职自荐信
2014/02/20 职场文书
小学生常见病防治方案
2014/06/06 职场文书
公司领导班子召开党的群众路线教育实践活动总结大会新闻稿
2014/10/21 职场文书
奠基仪式致辞
2015/07/30 职场文书
大学文艺委员竞选稿
2015/11/19 职场文书
2016年小学推普宣传周活动总结
2016/04/06 职场文书
涨工资申请书应该怎么写?
2019/07/08 职场文书