Vue.js之slot深度复制详解
前言
在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也能提供这个函数,免得大家踩坑。以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。
上一篇: 利用Docker制作Nginx+PHP镜像的步骤详解
下一篇: Docker 端口映射详细介绍