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类的静态属性和实例属性的理解
Oct 01 Javascript
简洁短小的 JavaScript IE 浏览器判定代码
Mar 21 Javascript
基于jquery的二级联动菜单实现代码
Apr 25 Javascript
jquery 笔记 事件
Nov 02 Javascript
js利用事件的阻止冒泡实现点击空白模态框的隐藏
Jan 24 Javascript
JS下载文件|无刷新下载文件示例代码
Apr 17 Javascript
node.js中的Socket.IO使用实例
Nov 04 Javascript
浅谈用Webpack路径压缩图片上传尺寸获取的问题
Feb 22 Javascript
JavaScript+H5实现微信摇一摇功能
May 23 Javascript
小程序实现多列选择器
Feb 15 Javascript
详解小程序退出页面时清除定时器
Apr 28 Javascript
javascript导出csv文件(excel)的方法示例
Aug 25 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快速查找数据库中恶意代码的方法
2015/04/01 PHP
学习php设计模式 php实现观察者模式(Observer)
2015/12/09 PHP
PHP构造函数与析构函数用法示例
2016/09/28 PHP
PHP基于openssl实现的非对称加密操作示例
2019/01/11 PHP
实例讲解PHP表单
2020/06/10 PHP
jQuery EasyUI 的EasyLoader功能介绍
2010/09/12 Javascript
jquery处理json对象
2014/11/03 Javascript
jQuery实现tab标签自动切换的方法
2015/02/28 Javascript
AngularJS使用ngMessages进行表单验证
2015/12/27 Javascript
详解javascript高级定时器
2015/12/31 Javascript
jQuery+PHP实现微信转盘抽奖功能的方法
2016/05/25 Javascript
使用 jQuery.ajax 上传带文件的表单遇到的问题
2016/10/31 Javascript
socket.io学习教程之深入学习篇(三)
2017/04/29 Javascript
vue 设置proxyTable参数进行代理跨域
2018/04/09 Javascript
详解微信小程序调起键盘性能优化
2018/07/24 Javascript
jQuery插件实现非常实用的tab栏切换功能【案例】
2019/02/18 jQuery
关于vue组件事件属性穿透详解
2019/10/28 Javascript
jquery实现吸顶导航效果
2020/01/08 jQuery
[00:21]DOTA2亚洲邀请赛 Logo演绎
2015/02/07 DOTA
深入浅析Python字符编码
2015/11/12 Python
JPype实现在python中调用JAVA的实例
2017/07/19 Python
python如何读写csv数据
2018/03/21 Python
Python OpenCV处理图像之滤镜和图像运算
2018/07/10 Python
python @propert装饰器使用方法原理解析
2019/12/25 Python
Java Spring项目国际化(i18n)详细方法与实例
2020/03/20 Python
Django静态资源部署404问题解决方案
2020/05/11 Python
Python3爬虫中关于Ajax分析方法的总结
2020/07/10 Python
Finishline官网:美国一家领先的运动品牌鞋类、服装零售商
2016/07/20 全球购物
C#如何判断当前用户是否输入某个域
2015/12/07 面试题
酒店经理职责
2014/01/30 职场文书
运动会致辞稿50字
2014/02/04 职场文书
高考备战决心书
2014/03/11 职场文书
教育系统干部作风整顿心得体会
2014/09/09 职场文书
2016党员学习作风建设心得体会
2016/01/21 职场文书
动画电影《龙珠超 超级英雄》延期上映
2022/03/20 日漫
在ubuntu下安装go开发环境的全过程
2022/08/05 Golang