伯索云学堂
224.22MB · 2026-02-15
在Vue组件化开发中,插槽(Slot)是一种强大的内容分发机制,它允许父组件向子组件传递任意模板内容,让子组件的结构更加灵活和可复用。你可以把插槽想象成子组件中预留的“占位符”,父组件可以根据需要在这些占位符中填充不同的内容,就像给积木玩具替换不同的零件一样。
插槽的核心思想是组件的结构与内容分离:子组件负责定义整体结构和样式,父组件负责提供具体的内容。这种设计让组件能够适应更多不同的场景,同时保持代码的可维护性。
默认插槽是最基础的插槽类型,它没有具体的名称,父组件传递的所有未指定插槽名的内容都会被渲染到默认插槽的位置。
子组件(FancyButton.vue)
<template>
<button class="fancy-btn">
<slot></slot> <!-- 插槽出口:父组件的内容将在这里渲染 -->
</button>
</template>
<style scoped>
.fancy-btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
background-color: #42b983;
color: white;
cursor: pointer;
}
</style>
父组件使用
<template>
<FancyButton>
Click me! <!-- 插槽内容:将被渲染到子组件的slot位置 -->
</FancyButton>
</template>
最终渲染出的HTML结构:
<button class="fancy-btn">Click me!</button>
在父组件没有提供任何内容时,我们可以为插槽设置默认内容,确保组件在任何情况下都能正常显示。
子组件(SubmitButton.vue)
<template>
<button type="submit" class="submit-btn">
<slot>Submit</slot> <!-- 默认内容:当父组件没有传递内容时显示 -->
</button>
</template>
父组件使用
<template>
<!-- 不传递内容,显示默认的"Submit" -->
<SubmitButton />
<!-- 传递内容,覆盖默认值 -->
<SubmitButton>Save Changes</SubmitButton>
</template>
当组件的结构比较复杂,包含多个需要自定义的区域时,默认插槽就不够用了。这时我们可以使用具名插槽,为每个插槽分配唯一的名称,让父组件能够精准地控制内容渲染到哪个位置。
子组件(BaseLayout.vue)
<template>
<div class="layout-container">
<header class="layout-header">
<slot name="header"></slot> <!-- 具名插槽:header -->
</header>
<main class="layout-main">
<slot></slot> <!-- 默认插槽:未指定名称的内容将在这里渲染 -->
</main>
<footer class="layout-footer">
<slot name="footer"></slot> <!-- 具名插槽:footer -->
</footer>
</div>
</template>
<style scoped>
.layout-container {
max-width: 1200px;
margin: 0 auto;
}
.layout-header {
padding: 16px;
border-bottom: 1px solid #eee;
}
.layout-main {
padding: 24px;
}
.layout-footer {
padding: 16px;
border-top: 1px solid #eee;
text-align: center;
}
</style>
父组件使用
<template>
<BaseLayout>
<!-- 使用#header简写指定内容渲染到header插槽 -->
<template #header>
<h1>我的博客</h1>
</template>
<!-- 未指定插槽名的内容将渲染到默认插槽 -->
<article>
<h2>Vue插槽详解</h2>
<p>这是一篇关于Vue插槽的详细教程...</p>
</article>
<!-- 使用#footer简写指定内容渲染到footer插槽 -->
<template #footer>
<p>© 2025 我的博客 版权所有</p>
</template>
</BaseLayout>
</template>
Vue还支持动态插槽名,你可以使用变
<template>
<BaseLayout>
<template #[dynamicSlotName]>
<p>动态插槽内容</p>
</template>
</BaseLayout>
</template>
<script setup>
import { ref } from 'vue'
const dynamicSlotName = ref('header') // 可以根据需要动态修改
</script>
在之前的内容中,我们了解到插槽内容只能访问父组件的数据(遵循JavaScript的词法作用域规则)。但在某些场景下,我们希望插槽内容能够同时使用父组件和子组件的数据,这时就需要用到作用域插槽。
作用域插槽允许子组件向插槽传递数据,父组件可以在插槽内容中访问这些数据。
子组件(UserItem.vue)
<template>
<div class="user-item">
<!-- 向插槽传递user对象作为props -->
<slot :user="user" :isAdmin="isAdmin"></slot>
</div>
</template>
<script setup>
import { ref } from 'vue'
const user = ref({
name: '张三',
age: 28,
avatar: 'https://via.placeholder.com/60'
})
const isAdmin = ref(true)
</script>
父组件使用
<template>
<!-- 使用v-slot指令接收插槽props -->
<UserItem v-slot="slotProps">
<img :src="slotProps.user.avatar" alt="用户头像" class="avatar">
<div class="user-info">
<h3>{{ slotProps.user.name }}</h3>
<p>年龄:{{ slotProps.user.age }}</p>
<span v-if="slotProps.isAdmin" class="admin-tag">管理员</span>
</div>
</UserItem>
</template>
为了让代码更简洁,我们可以使用ES6的解构语法直接提取插槽Props:
<template>
<UserItem v-slot="{ user, isAdmin }">
<img :src="user.avatar" alt="用户头像" class="avatar">
<div class="user-info">
<h3>{{ user.name }}</h3>
<p>年龄:{{ user.age }}</p>
<span v-if="isAdmin" class="admin-tag">管理员</span>
</div>
</UserItem>
</template>
具名插槽也可以传递Props,父组件需要在对应的具名插槽上接收:
子组件
<template>
<div class="card">
<slot name="header" :title="cardTitle"></slot>
<slot :content="cardContent"></slot>
</div>
</template>
<script setup>
import { ref } from 'vue'
const cardTitle = ref('卡片标题')
const cardContent = ref('这是卡片的内容...')
</script>
父组件
<template>
<Card>
<template #header="{ title }">
<h2>{{ title }}</h2>
</template>
<template #default="{ content }">
<p>{{ content }}</p>
</template>
</Card>
</template>
默认插槽是组件中没有指定名称的插槽,父组件传递的未指定插槽名的内容会被渲染到默认插槽的位置。示例:
<!-- 子组件 -->
<button><slot></slot></button>
<!-- 父组件 -->
<Button>点击我</Button>
具名插槽用于组件包含多个需要自定义的区域的场景,每个插槽有唯一的名称,父组件可以精准控制内容的渲染位置。父组件使用<template #插槽名>的语法传递内容到指定的具名插槽。
作用域插槽解决了插槽内容无法访问子组件数据的问题。工作原理:子组件在插槽出口上传递Props(类似组件Props),父组件使用v-slot指令接收这些Props,从而在插槽内容中访问子组件的数据。
在<slot>标签之间写入默认内容即可,当父组件没有传递内容时,默认内容会被渲染:
<slot>默认内容</slot>
v-slot指令只能用在<template>或组件标签上原因:v-slot指令只能用于<template>标签或组件标签,不能直接用于普通HTML元素。
解决办法:将v-slot指令移到<template>标签或组件标签上。例如:
<!-- 错误写法 -->
<div v-slot="slotProps">{{ slotProps.text }}</div>
<!-- 正确写法 -->
<template v-slot="slotProps">
<div>{{ slotProps.text }}</div>
</template>
原因:父组件尝试访问子组件未传递的插槽Props。 解决办法:
?.)避免报错:
<MyComponent v-slot="{ text }">
{{ text?.toUpperCase() }} <!-- 使用可选链操作符 -->
</MyComponent>
原因:
<slot name="header"></slot>。原因:当同时使用默认插槽和具名插槽时,直接为组件添加v-slot指令会导致编译错误,因为默认插槽的Props作用域会与具名插槽混淆。
解决办法:为默认插槽使用显式的<template>标签:
<!-- 错误写法 -->
<MyComponent v-slot="{ message }">
<p>{{ message }}</p>
<template #footer>
<p>{{ message }}</p> <!-- message 属于默认插槽,此处不可用 -->
</template>
</MyComponent>
<!-- 正确写法 -->
<MyComponent>
<template #default="{ message }">
<p>{{ message }}</p>
</template>
<template #footer>
<p>页脚内容</p>
</template>
</MyComponent>
cn.vuejs.org/guide/compo…