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 相关文章推荐
Javascript 匿名函数及其代码模式原理
Mar 19 Javascript
extjs 04_grid 单击事件新发现
Nov 27 Javascript
CheckBoxList多选样式jquery、C#获取选择项
Sep 06 Javascript
JQuery实现点击div以外的位置隐藏该div窗口
Sep 13 Javascript
JavaScript创建一个object对象并操作对象属性的用法
Mar 23 Javascript
jquery自定义插件——window的实现【示例代码】
May 06 Javascript
详解vue-router和vue-cli以及组件之间的传值
Jul 04 Javascript
如何理解Vue的作用域插槽的实现原理
Aug 19 Javascript
详解require.js配置路径的用法和css的引入
Sep 06 Javascript
通过vue写一个瀑布流插件代码实例
Sep 07 Javascript
windows下create-react-app 升级至3.3.1版本踩坑记
Feb 17 Javascript
JavaScript交换变量的常用方法小结【4种方法】
May 07 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
PHP简单判断iPhone、iPad、Android及PC设备的方法
2016/10/11 PHP
js 解决“options为空或不是对象”
2008/12/22 Javascript
高性能WEB开发 flush让页面分块,逐步呈现 flush让页面分块,逐步呈现
2010/06/19 Javascript
仿当当网淘宝网等主流电子商务网站商品分类导航菜单
2013/09/25 Javascript
PHP开发者必须掌握的6个关键字
2014/04/14 Javascript
js阻止事件追加的具体实现
2014/10/15 Javascript
jQuery背景插件backstretch使用指南
2015/04/21 Javascript
js调出上下文菜单的实例
2015/12/17 Javascript
几种经典排序算法的JS实现方法
2016/03/25 Javascript
Bootstrap 附加导航(Affix)插件实例详解
2016/06/01 Javascript
AngularJs 利用百度地图API 定位当前位置 获取地址信息
2017/01/18 Javascript
bootstrap css样式之表单
2017/01/19 Javascript
JS中的回调函数实例浅析
2018/03/21 Javascript
js input输入百分号保存数据库失败的解决方法
2018/05/26 Javascript
Bootstrap Table中的多选框删除功能
2018/07/15 Javascript
详解angular应用容器化部署
2018/08/14 Javascript
推荐一个基于Node.js的表单验证库
2019/02/15 Javascript
vue动态绘制四分之三圆环图效果
2019/09/03 Javascript
微信小程序swiper左右扩展各显示一半代码实例
2019/12/05 Javascript
详谈Python3 操作系统与路径 模块(os / os.path / pathlib)
2018/04/26 Python
python爬取Ajax动态加载网页过程解析
2019/09/05 Python
在python中计算ssim的方法(与Matlab结果一致)
2019/12/19 Python
keras slice layer 层实现方式
2020/06/11 Python
世界上最全面的草药补充剂和顶级品牌维生素网站:HerbsPro
2019/01/20 全球购物
精选奢华:THE LIST
2019/09/05 全球购物
商得四方公司面试题(gid+)
2014/04/30 面试题
关键字throw与throws的用法差异
2016/11/22 面试题
机电一体化应届生求职信
2014/08/09 职场文书
在职证明书范本(2014新版)
2014/09/25 职场文书
关于运动会的广播稿50字
2014/10/17 职场文书
2015国庆节66周年演讲稿
2015/03/20 职场文书
工作简报范文
2015/07/21 职场文书
2016教师六五普法学习心得体会
2016/01/21 职场文书
《弟子规》读后感:知廉耻、明是非、懂荣辱、辨善恶
2019/12/03 职场文书