我们之前在学习组件的时候知道可以通过props
来传递属性:
<template>
<MyButton text="发送请求" />
</template>
<template>
<button>{{ text }}</button>
</template>
<script>
export default{
props: ["text"]
}
</script>
插槽基础
那么如何给子传递传递更丰富的信息呢?例如给子组件传递一张图片,一段 HTML…
Vue 给我们提供了<slot></slot>
组件用于在子组件内部进行占位。
<template>
<MyButton>发送请求</MyButton>
</template>
<template>
<button>
<slot></slot>
</button>
</template>
<!-- 最后的渲染结果 -->
<button>发送请求</button>
:::info
<slot></slot>
是组件化中一个比较形象的占位,相当于内容占位标签,组件化内部的一种扩展功能,表示了父元素提供的插槽内容将在哪里被渲染。<slot></slot>
可以是任意的内容:一段文本、一个组件、一段 HTML 都是可以的,我们只要理解<slot></slot>
就是在子组件内的占位标签就可以了。
:::
如果子组件内部没有提供<slot></slot>
进行占位,那么你在调用组件的时候在标签中间写的东西都将无效:
<template>
<MyButton>发送请求</MyButton>
</template>
<template>
<!-- 不提供 slot -->
<button></button>
</template>
<!-- 最后的渲染结果 -->
<button></button>
使用插槽的时候可以访问当前组件中的内容,但是不能访问子组件的内容:
<template>
<MyButton>
<span>{{ btnText }}</span>
</MyButton>
</template>
<script>
export default{
data(){
return{
btnText: "发送请求"
}
}
}
</script>
<template>
<!-- 🙅 错误!!! -->
<button>{{ btnText }}</button>
</template>
理解插槽的另一种方式是和下面的 JavaScript 函数作类比,其概念是类似的:
// 父元素传入插槽内容
FancyButton('Click me!')
// FancyButton 在自己的模板中渲染插槽内容
function FancyButton(slotContent) {
return `
<button class="fancy-btn">
${slotContent}
</button>`
}
我们还可以给插槽设置默认的内容,当父组件调用的时候子组件内不传递任何的内容将会使用默认的内容:
<template>
<MyButton></MyButton>
</template>
<template>
<button>
<slot>默认文本</slot>
</button>
</template>
<!-- 最后的渲染结果 -->
<button>默认文本</button>
具名插槽
理解具名的意思:对于函数来说有两大分类,分别是「匿名函数」和「具名」函数。
// 匿名函数赋值给 test
var test = function () {};
// 将具名函数 test2 赋值给 test1
var test1 = function test2() {};
有没有办法我又要使用template
而且slot
也可以生效呢?
当你并没有给<slot></slot>
设置name
名称的时候,slot
会产生一个隐式的名称:default
<MyButton>
<template v-slot:default>
<span>提交</span>
</template>
</MyButton>
你也可以给子组件的slot
组件加上随意的名称,例如我有一个布局组件,我给每一个slot
都加上了name
属性:
<div class="container">
<header>
<slot name="header">默认头部信息</slot>
</header>
<main>
<!-- 该 slot 会默认产生一个名为 default 的名称 -->
<slot>默认主体信息</slot>
</main>
<footer>
<slot name="footer">默认底部信息</slot>
</footer>
</div>
而我们在父组件使用该组件的时候就可以根据name
来选择传递的内容。
要为具名插槽传入内容,我们需要使用一个含**v-slot**
指令的**<template>**
元素,并将目标插槽的名字传给该指令:
<Layout>
<template v-slot:header>
<img src="/logo.png" />
</template>
</Layout>
v-slot
也可以简写为#
:
<Layout>
<template v-slot:header>
<img src="/logo.png" />
</template>
<!-- 使用默认的 slot , 名字为 default -->
<template #default>
<p>A paragraph for the main content.</p>
</template>
<template #footer>
<p>This is page footer content.</p>
</template>
</Layout>
当一个组件同时接收默认插槽和具名插槽时,所有位于顶级的非**<template>**
节点都被隐式地视为默认插槽的内容。所以上面也可以写成:
<Layout>
<template v-slot:header>
<img src="/logo.png" />
</template>
<!-- 隐式的默认插槽 -->
<p>A paragraph for the main content.</p>
<template #footer>
<p>This is page footer content.</p>
</template>
</Layout>
最终的渲染结果就是这样的:
<div class="container">
<header>
<img src="/logo.png" />
</header>
<main>
<p>A paragraph for the main content.</p>
</main>
<footer>
<p>This is page footer content.</p>
</footer>
</div>
作用域插槽
在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽。
我们可以给<slot></slot>
传递属性供父组件进行使用:
<div>
<!-- 和普通组件一样,在标签上传递属性 -->
<slot :text="greetingMessage" :count="1"></slot>
</div>
父组件可以来接收<slot></slot>
传递过来的值:
<MyComponent v-slot="props">
{{ props }}
<!-- { 'text':"xxx", 'count': 'xxx' } -->
</MyComponent>
子组件通过插槽传递来的数据会包装到**props**
对象上!!!
如果子组件内存在多个具名插槽,我们也可以进行搭配使用:
<MyComponent>
<!-- #header="headerProps" 是 v-slot:header="headerProps" 的简写形式 -->
<template #header="headerProps">
{{ headerProps }}
</template>
<template #default="defaultProps">
{{ defaultProps }}
</template>
<template #footer="footerProps">
{{ footerProps }}
</template>
</MyComponent>
:::warning
⚠️ 注意
注意插槽上的name
是一个 Vue 特别保留的 attribute,不会作为 props 传递给插槽。因此最终headerProps
的结果将不包含name
属性!!!
:::
如果你混用了具名插槽与默认插槽,则需要为默认插槽使用显式的**<template>**
标签。尝试直接为组件添加**v-slot**
指令将导致编译错误:
<!-- 该模板无法编译 -->
<template>
<MyComponent v-slot="{ message }">
<p>{{ message }}</p>
<template #footer>
<!-- message 属于默认插槽,此处不可用 -->
<p>{{ message }}</p>
</template>
</MyComponent>
</template>