说说你对slot的理解?slot使用场景有哪些?

在这里插入图片描述

一、slot是什么

在HTML中slot元素,作为Web Components技术套件的一部分,是Web组件内的一个占位符

该占位符可以在后期使用自己的标记语言填充

举个栗子

<template id="element-details-template">
	<slot name="element-name">Slot template</slot>
</template>
<element-details>
	<span slot="element-name">1</span>
</element-details>
<element-details>
	<span slot="element-name">2</span>
</element-details>

template不会展示到页面中,需要用先获取它的引用,然后添加到DOM中

customElements.define('element-details',
	class extends HTMLElement{
		constructor(){
			super();
			const template = document
				.getElementById('element-details-template')
				.content;
			const shadowRoot = this.attachShadow({mode:'open'})
				.appendChild(template.cloneNode(true))			  										
	}
})

在Vue中的概念也是如此

Slot艺名插槽,花名"占坑",我们可以理解为slot在组件模板中占好了位置,当使用该组件标签时候,组件标签里面的内容就会自动填充(替换组件模板中slot位置),作为承载分发内容的出口

可以将其类比为插卡式的FC游戏机,游戏机暴露卡槽(插槽)让用户插入不同的游戏磁条(自定义内容)

二、使用场景

通过插槽可以让用户可以拓展组件,去更好地复用组件和对其做定制化处理

如果父组件在使用到一个复用组件的时候,获取这个组件在不同地方有少量的更改,如果去重写组件是一件不明智的事情

通过slot插槽向组件内部指定位置传递内容,完成这个复用组件在不同场景的应用

比如布局组件、表格列、下拉选、弹框显示内容等

三、分类

slot可以分为一下三种:

  • 默认插槽
  • 具名插槽
  • 作用域插槽

默认插槽
子组件用< slot >标签来确定渲染位置,标签里面可以放DOM结构,当父组件使用的时候没有往插槽传入内容,标签内DOM结构就会显示在页面

父组件在使用的时候,直接在子组件的标签内写入内容即可

子组件Child.vue

<template>
	<slot>
		<p>插槽后备的容</p>
	</slot>
</template>

父组件

<Child>
	<div>默认插槽</div>
</Child>

具名插槽
子组件用name属性来表示插槽的名字,不传为默认插槽

父组件中在使用时在默认插槽的基础上加上slot属性,值为子组件插槽name属性值

子组件Child.vue

<template>
	<slot>插槽后备的内容</slot>
	<slot name="content">插槽后备的内容</slot>
</tamplate>

父组件

<Child>
	<template v-slot:default>具名插槽
</template>
	<!--具名插槽 插槽名做参数-->
	<template v-slot:content>内容....</template>
</Child>

作用域插槽
子组件在作用域上绑定属性来将自组件的信息传给父组件使用,这些属性会被挂在父组件v-slot接收的对象上

父组件中在使用时通过 v-slot: (简写:#)获取子组件的信息,在内容中使用

子组件Child.vue

<template>
	<slot name="footer" testProps="子组件的值">
		<h3>没传footer插槽</h3>
</slot>
</template>

父组件

<Child>
	<!--把v-slot的值指定为作用域上下文对象-->
	<template v-slot:default="slotProps">
		来自子组件数据:{{slotProps.testProps}}
</template>
	<template #default="slotProps">
		来自子组件数据:{{slotProps.testProps}}
</template>
</Child>

小结:

  • v-slot属性只能在< template >上使用,但在只有默认插槽时可以在组加标签上使用
  • 默认插槽名为 default ,可以省略 default 直接写 v-slot
  • 缩写为 # 时不能不写参数,写成 #default
  • 可以通过解构获取 v-slot={user} ,还可以重命名 v-slot="{user:newName}" 和定义默认值 v-slot="{user = ‘默认值’}"

四、原理分析

slot 本质上时返回 VNode 的函数,一般情况下, Vue 中的组件要渲染到页面上需要经过 template -> render function -> VNode -> DOM 过程,这里看看 slot 如何实现:

编写一个 buttonCounter 组件,使用匿名插槽

Vue.component('button-counter',{
	template:'<div><slot>我是默认内容</slot></div>'
})

使用该组件

new Vue({
	el:"#app",
	template:"<button-counter><span>我是slot传入的内容</span></button-counter>",
	components:{buttonCounter}
})

获取 buttonCounter 组件渲染函数

(function anonymous){
	with(this){return _c('div',[_t("default",[_v('我是默认内容')])],2)}
}

_v 表示创建普通文本节点,_t 表示渲染插槽的函数

渲染插槽函数 renderSlot (做了简化)

function renderSlot(name,fallback,props,bindObject){
	//得到渲染插槽内容的函数
	var scopedSlotFn = this.$scopedSlots[name];
	var nodes;
	//如果存在插槽渲染函数,则执行插槽渲染函数,生成nodes节点返回
	//否则使用默认值
	nodes = scopedSlotFn(props) || fallback;
	return nodes;
}

name 属性表示定义插槽的名字,默认值为 default ,fallback 表示子组件中的 slot 节点的默认值

关于 this.$scopedSlots 是什么,我们可以先看看 vm.slot

function initRender(vm){
	...
	vm.$slots = resolveSlots(options._renderChildren,renderContext);
}

resolveSlots 函数会对 children 节点做归类和过滤处理,返回 slots

function resolveSlots(children,context){
	if(!children || !children.length){
		return {}
	}
	var slots = {};
	for(var i = 0,l = children.length;i < l;i++){
		var child = children[];
		var data = child.data;
		//remove slot attribute if the node is resolved as a Vue slot node
		if(data && data.attrs && data.attrs.slot){
			delete data.attrs.slot;
		}
		//named slots should noly be respected if the vnode was rendered in the same context
		if((child.context === context || child.fnContext === context) && data && data.slot != null){
			//如果slot存在(slot="header")则拿对应的值作为key
			var name = data.slot;
			var slot = (slots[name] || (slots[name] = []));
			//如果是template元素,则把template的children添加进数组中,这也就是为什么你写的template标签并不会被渲染成另一个标签到页面
			if(child.tag === 'template'){
				slot.push.apply(slot, child.children || [])
			}else{
				slot.push(child)
			}
		}else{
			//如果没有就默认是default
			(slots.default || (slots.default = [])).push(child);
		}
	}
	//ignore slots that contains only whitespace
	for(var name$1 in slots){
		if(slots[name$1].every(isWhitespace)){
			delete slots[name$1]
		}
	}
	return slots
}

_render 渲染函数通过 normalizeScopedSlots 得到 vm.$scopedSlots

vm.$scopedSlots = normalizeScopedSlots(
	_parenVnode.data.scopedSlots,
	vm.$slots,
	vm.$scopedSlots
)

作用域插槽中父组件能够得到子组件的值是因为在 renderSlot 的时候执行会传入 props ,也就是上述 _t 第三个参数,父组件则能够得到子组件传递过来的值
参考文献1
参考文献2
参考文献3
参考文献4


版权声明:本文为webblock原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。