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 相关文章推荐
关于捕获用户何时点击window.onbeforeunload的取消事件
Mar 06 Javascript
Javascript基础教程之for循环
Jan 18 Javascript
js图片轮播特效代码分享
Sep 07 Javascript
xmlplus组件设计系列之分隔框(DividedBox)(8)
May 02 Javascript
利用Mongoose让JSON数据直接插入或更新到MongoDB
May 03 Javascript
Vue实现简易翻页效果源码分享
Nov 08 Javascript
webpack4.x CommonJS模块化浅析
Nov 09 Javascript
JS异步执行结果获取的3种解决方式
Feb 19 Javascript
JS实现可用滑块滑动的缓动图代码
Sep 01 Javascript
iSlider手机端图片滑动切换插件使用详解
Dec 24 Javascript
微信小程序 获取手机号 JavaScript解密示例代码详解
May 14 Javascript
ES6学习教程之Promise用法详解
Nov 22 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
福利彩票幸运号码自动生成器
2006/10/09 PHP
php面向对象全攻略 (三)特殊的引用“$this”的使用
2009/09/30 PHP
Sorting Array Values in PHP(数组排序)
2011/09/15 PHP
PHP中的数组处理函数实例总结
2016/01/09 PHP
PHP微信开发用Cache 解决数据缓存
2016/07/11 PHP
PHP设计模式之单例模式定义与用法分析
2019/03/26 PHP
前淘宝前端开发工程师阿当的PPT中有JS技术理念问题
2010/01/15 Javascript
再次分享18个非常棒的jQuery表格插件
2011/04/10 Javascript
Notify - 基于jquery的消息通知插件
2011/10/18 Javascript
jquery和js实现对div的隐藏和显示方法
2014/09/26 Javascript
Node.js Stream ondata触发时机与顺序的探索
2019/03/08 Javascript
Vue实现简单的跑马灯
2020/05/25 Javascript
Vue-cli 移动端布局和动画使用详解
2020/08/10 Javascript
Python的ORM框架中SQLAlchemy库的查询操作的教程
2015/04/25 Python
Python实现SSH远程登陆,并执行命令的方法(分享)
2017/05/08 Python
python安装模块如何通过setup.py安装(超简单)
2018/05/05 Python
基于Django与ajax之间的json传输方法
2018/05/29 Python
基于pycharm导入模块显示不存在的解决方法
2018/10/13 Python
Python3 SSH远程连接服务器的方法示例
2018/12/29 Python
django-rest-framework解析请求参数过程详解
2019/07/18 Python
Python实现生成密码字典的方法示例
2019/09/02 Python
python生成器推导式用法简单示例
2019/10/08 Python
HTML5中FileReader接口使用方法实例详解
2017/08/26 HTML / CSS
HTML5实现移动端点击翻牌功能
2020/10/23 HTML / CSS
乌克兰在线商店的价格比较:Price.ua
2019/07/26 全球购物
俄罗斯茶和咖啡网上商店:Tea.ru
2021/01/26 全球购物
银行学习十八大感想
2014/01/11 职场文书
营销总监岗位职责范本
2014/02/26 职场文书
支行行长竞聘演讲稿
2014/05/15 职场文书
敬老模范事迹
2014/05/21 职场文书
四风对照检查剖析材料
2014/10/07 职场文书
社团个人总结范文
2015/03/05 职场文书
七年级话题作文之执着
2019/11/19 职场文书
Nginx配置之实现多台服务器负载均衡
2021/08/02 Servers
一文搞懂Golang 时间和日期相关函数
2021/12/06 Golang
MySQL三种方式实现递归查询
2022/04/18 MySQL