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 相关文章推荐
显示、隐藏密码
Jul 01 Javascript
用js 让图片在 div或dl里 居中,底部对齐
Jan 21 Javascript
SOSO地图JS画出标注和中心点以html形式运行
Aug 09 Javascript
JS 毫秒转时间示例代码
Sep 22 Javascript
javascript获取web应用根目录的方法
Feb 12 Javascript
实例讲解JQuery中this和$(this)区别
Dec 08 Javascript
JS实现的生成随机数的4个函数分享
Feb 11 Javascript
jQuery实现冻结表格行和列
Apr 29 Javascript
Vue.js 和 MVVM 的注意事项
Nov 07 Javascript
微信小程序云开发之新手环境配置
May 16 Javascript
详解微信小程序动画Animation执行过程
Sep 23 Javascript
Handtrack.js库实现实时监测手部运动(推荐)
Feb 08 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之APC缓存详细介绍 apc模块安装
2014/01/13 PHP
php中通过DirectoryIterator删除整个目录的方法
2015/03/13 PHP
javascript 判断字符串是否包含某字符串及indexOf使用示例
2013/10/18 Javascript
JavaScript如何动态创建table表格
2020/08/02 Javascript
仅一个form表单 js实现注册信息依次填写提交功能
2016/06/12 Javascript
静态页面html中跳转传值的JS处理技巧
2016/06/22 Javascript
JS全局变量和局部变量最新解析
2016/06/24 Javascript
清除输入框内的空格
2016/12/21 Javascript
Angular使用ng-messages与PHP进行表单数据验证
2016/12/28 Javascript
详解使用vue实现tab 切换操作
2017/07/03 Javascript
webpack踩坑之路图片的路径与打包
2017/09/05 Javascript
nodejs项目windows下开机自启动的方法
2017/11/22 NodeJs
nodejs基于express实现文件上传的方法
2018/03/19 NodeJs
使用webpack-dev-server处理跨域请求的方法
2018/04/18 Javascript
AngularJS 前台分页实现的示例代码
2018/06/07 Javascript
layui实现点击按钮给table添加一行
2018/08/10 Javascript
使用weixin-java-tools完成微信授权登录、微信支付的示例
2018/09/26 Javascript
node Buffer缓存区常见操作示例
2019/05/04 Javascript
Vue动态面包屑功能的实现方法
2019/07/01 Javascript
JointJS JavaScript流程图绘制框架解析
2019/08/15 Javascript
vue组件内部引入外部js文件的方法
2020/01/18 Javascript
JavaScript Window窗口对象属性和使用方法
2020/01/19 Javascript
微信小程序修改checkbox的样式代码实例
2020/01/21 Javascript
JS+HTML5本地存储Localstorage实现注册登录及验证功能示例
2020/02/10 Javascript
[05:17]DOTA2睡衣妹卖萌求签名 CJ第二天全明星影像
2013/07/28 DOTA
[48:28]完美世界DOTA2联赛循环赛FTD vs Magma第二场 10月30日
2020/10/31 DOTA
Python Tkinter基础控件用法
2014/09/03 Python
在Apache服务器上同时运行多个Django程序的方法
2015/07/22 Python
Python使用剪切板的方法
2017/06/06 Python
python爬虫 基于requests模块的get请求实现详解
2019/08/20 Python
Python3 实现减少可调用对象的参数个数
2019/12/20 Python
详解Canvas 跨域脱坑实践
2018/11/07 HTML / CSS
护士自荐信怎么写
2013/10/18 职场文书
高中学生期末评语
2014/04/25 职场文书
社区敬老月活动总结
2015/05/07 职场文书
志愿者服务宣传标语口号
2015/12/26 职场文书