这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战
一、什么是Vue插槽
插槽是Vue中一个非常强大的功能,但是之前学的时候理解的不是很清楚,然后边学边忘。这篇博客主要是梳理学习插槽的内容。
我们先来说一说什么是插槽?
Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将
<slot>元素作为承载分发内容的出口。
感觉不是特别好理解?没关系
二、梳理几个问题
1. 插槽的作用是什么?为什么要有插槽功能(解决了什么问题)?
定义两个组件 Index 和 Inner
// Index.vue
<template>
<div class="hello">
<Inner>
<p>想把这个p标签插入到Inner</p>
</Inner>
</div>
</template>
// Inner.vue
<template>
<div class="hello">
<ul>
<li><span>列表项01:</span>vue</li>
<li><span>列表项02:</span>vue</li>
<li><span>列表项03:</span>vue</li>
</ul>
</div>
</template>
如果现在我们想在<Inner><Inner>内放置一些内容,结果会是怎样?
输出内容还是在 <Inner> 组件中的内容,在 <Inner> 标签内写的内容没起作用。
这就是插槽出现的作用。
我们在 <Inner> 组件中加入 插槽 <slot></slot>
比如在列表项01和02之间加入,可以发现<Inner> 标签中的内容已经加入到<Inner>组件中了,而且正好在列表项01和02之间。
// Inner.vue
<template>
<div class="hello">
<ul>
<li><span>列表项01:</span>vue</li>
<slot></slot>
<li><span>列表项02:</span>vue</li>
<li><span>列表项03:</span>vue</li>
</ul>
</div>
</template>
到现在,我们知道了什么是插槽:
插槽就是Vue实现的一套内容分发的API,将<slot></slot>元素作为承载分发内容的出口。
简单说,我们可以在组件标签内定义需要的内容,通过插槽加入到组件内部中。
组件内部的<slot></slot>元素就好像一个传送门,也就是所谓的槽,提供了内容的入口,也决定了内容的位置。
组件标签中定义的内容,通过这个“传送门”就可以加入到组件内部中。
2. 插槽中的“插件”在哪里?“槽”在哪里?
这个问题其实在上一个问题中就回答过了。
插槽中的“插件”就是组件标签中的内容。
插槽中的“槽”就是在组件中的<slot></slot>元素。
3. 为什么没有<slot></slot>元素,标签中的内容就无法加入?
你可以这样想一想,
组件中的内容都是排列好的。这个时候你要加入内容,
如果没有<slot></slot>元素,那这个内容应该放在什么位置呢?如果不加约定,可能会打乱原来排列好的内容。
如果
<navigation-link>的template中没有包含一个<slot>元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
Vue的作者可能就是这样设计的,在没有<slot></slot>元素的时候,就不渲染组件标签中的内容。
如果是你来设计,也许也可以设计成默认渲染在内容的最上面(当然这样可能会引入其他的问题)
所以提这个问题主要还是说不要太纠结,参考官方文档就可以。 (如果是真的有重要的考量也欢迎大佬在评论区提出)
三、插槽的分类
1. 默认插槽
上面介绍的例子中用的就是默认插槽,组件标签中的所有内容会通过默认插槽统一渲染。这里就不做过多介绍了。
2. 具名插槽
如果说要将不同的内容放在不同的位置,默认插槽就显得力不从心了。 这个时候就要用到具名插槽了。
顾名思义,具名插槽就是给插槽加一个名字。
我们给“插件”加一个名字,再给“槽”加上相同的名字,这样就可以一一对应了。
// Index.vue
<template>
<div class="hello">
<Inner>
<template v-slot:apple><p>这个是apple</p></template>
<template v-slot:banana><p>这个是banana</p></template>
<template v-slot:orange><p>这个是orange</p></template>
<template ><p>这个是没有指定具名插槽的内容</p></template>
<template ><p>这一个也是没有指定具名插槽的内容</p></template>
</Inner>
</div>
</template>
// Inner.vue
<template>
<div class="hello">
<ul>
<li><span>列表项01:</span>vue</li>
<slot name="apple"></slot>
<li><span>列表项02:</span>vue</li>
<slot name="banana"></slot>
<li><span>列表项03:</span>vue</li>
<slot name="orange"></slot>
<br>
<p>这里是默认插槽</p>
<slot></slot>
<br>
<p>这里是名为 default 的具名插槽</p>
<slot name="default"></slot>
</ul>
</div>
</template>
从这里我们可以发现
- 具名插槽可以根据名称渲染对应的内容
- 没有定义名称的内容会被默认插槽统一渲染
- 默认插槽其实也是一个具名插槽,名称为 default
3. 作用域插槽
作用域插槽也是之前非常难理解的一个概念。
简单来说就是
插槽中内容的流动方向是从组件标签传到组件内部, 而作用域插槽则让作用域反向流动,从组件内部传到组件标签内。 可以在组件标签内访问组件内部的变量。
有时让插槽内容能够访问子组件中才有的数据是很有用的。
我们还是从需求来说起
我们想在插槽内容中访问apple的信息,然后报错了。
// Index.vue
<template>
<div class="hello">
<Inner>
<template v-slot:apple><p>这个是apple,{{apple.msg}}</p></template>
<template ><p>这个是没有指定具名插槽的内容</p></template>
</Inner>
</div>
</template>
// Inner.vue
<template>
<div class="hello">
<ul>
<li><span>列表项01:</span>vue</li>
<slot name="apple"></slot>
<li><span>列表项02:</span>vue</li>
<li><span>列表项03:</span>vue</li>
<slot></slot>
</ul>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
},
data() {
return {
apple:{
msg: "味道非常好",
}
};
},
};
</script>
这个是因为编译作用域
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
在 <Index> 中 是访问不到 <Inner> 中定义的数据的。
这个时候就要用到作用域插槽了
我们可以将 需要的数据 作为
<slot>元素的一个 attribute 绑定上去绑定在
<slot>元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的v-slot来定义我们提供的插槽 prop 的名字:
// Index.vue
<template>
<div class="hello">
<Inner>
<template v-slot:apple="slotProps"
><p>
{{
`这个是apple,${slotProps.apple.msg},价格是${slotProps.price},一共是${slotProps.weightCount}kg`
}}
</p>
</template>
<template><p>这个是没有指定具名插槽的内容</p></template>
</Inner>
</div>
</template>
// Inner.vue
<template>
<div class="hello">
<ul>
<li><span>列表项01:</span>vue</li>
<slot name="apple" :apple="apple" :price="price" :weightCount="weightCount"></slot>
<li><span>列表项02:</span>vue</li>
<li><span>列表项03:</span>vue</li>
<slot></slot>
</ul>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {},
data() {
return {
apple: {
msg: "味道非常好",
},
price: "10元/kg",
weightCount: 1000,
};
},
};
</script>
关键的两步操作
- 将要传出的数据作为
<slot>元素的一个 attribute 绑定上去,称为插槽 prop
<slot name="apple" :apple="apple" :price="price" :weightCount="weightCount"></slot>
- 在组件标签内(父级作用域中),可以使用带值的
v-slot来获取所有的插槽 prop,v-slot会把所有插槽 prop聚合为一个对象。
<template v-slot:apple="slotProps">
<p>
{{
`这个是apple,${slotProps.apple.msg},价格是${slotProps.price},一共是${slotProps.weightCount}kg`
}}
</p>
</template>
这里我们把这个插槽 prop对象定义为 slotProps(这个你可以自定义),然后我们就可以通过这个对象来访问组件内部的是数据了。
如果用过element-ui的同学,一定知道表格就是这样来的!! 这个就是表格的实现原理。
四、插槽的其他特性
关于插槽的其他特性,建议大家查阅官方文档,上面已经写的非常清楚了。
后备内容
独占默认插槽的缩写语法
解构插槽 Prop
动态插槽名
具名插槽的缩写
五、记录一些小问题
1.插槽内容的样式问题 插槽内容的样式是以父组件为准还是以子组件为准
// Index.vue
<template>
<div class="hello">
<Inner>
<p>想把这个p标签插入到Inner</p>
</Inner>
</div>
</template>
// Index.vue
p {
color: #42b983;
}
// Inner.vue
<template>
<div class="hello">
<ul>
<li><span>列表项01:</span>vue</li>
<li><span>列表项02:</span>vue</li>
<li><span>列表项03:</span>vue</li>
</ul>
</div>
</template>
// Inner.vue
p {
color: #1e80ff;
}
参考资料