插槽(Slots)与作用域插槽
插槽(Slots)是 Vue 3 中实现组件内容分发的强大机制,允许父组件向子组件注入自定义内容。普通插槽提供静态内容,而作用域插槽(Scoped Slots)则通过数据绑定实现动态交互。本节将详细讲解插槽和作用域插槽的用法、实现方式和应用场景,帮助你掌握这一组件化开发的利器。
什么是插槽?
插槽是子组件中的占位符,允许父组件在渲染时插入内容。Vue 3 支持默认插槽、具名插槽和作用域插槽。
基本用法:默认插槽
子组件(Child.vue)
<template>
<div>
<h2>子组件</h2>
<slot>默认内容</slot>
</div>
</template>
父组件
<template>
<div>
<Child>
<p>来自父组件的内容</p>
</Child>
</div>
</template>
<script>
import Child from './Child.vue';
export default {
components: { Child }
};
</script>
- 效果:子组件渲染:
子组件 来自父组件的内容 - 解析:
<slot>是插槽占位符。- 未提供内容时,显示“默认内容”。
具名插槽
具名插槽允许多个插槽共存,使用 name 属性区分。
示例:多插槽布局
子组件(Layout.vue)
<template>
<div>
<header>
<slot name="header">默认头部</slot>
</header>
<main>
<slot>默认内容</slot>
</main>
<footer>
<slot name="footer">默认底部</slot>
</footer>
</div>
</template>
父组件
<template>
<div>
<Layout>
<template #header>
<h1>自定义头部</h1>
</template>
<p>主要内容</p>
<template #footer>
<p>自定义底部</p>
</template>
</Layout>
</div>
</template>
<script>
import Layout from './Layout.vue';
export default {
components: { Layout }
};
</script>
- 效果:
<header><h1>自定义头部</h1></header> <main><p>主要内容</p></main> <footer><p>自定义底部</p></footer> - 解析:
#header是v-slot:header的简写。- 无
name的<slot>为默认插槽。
作用域插槽
作用域插槽通过子组件向父组件传递数据,实现动态内容渲染。
基本用法
子组件(List.vue)
<template>
<ul>
<slot v-for="item in items" :item="item" :key="item.id" />
</ul>
</template>
<script>
import { reactive } from 'vue';
export default {
setup() {
const items = reactive([
{ id: 1, name: '苹果' },
{ id: 2, name: '香蕉' }
]);
return { items };
}
};
</script>
父组件
<template>
<div>
<List v-slot="{ item }">
<li>{{ item.name }} (ID: {{ item.id }})</li>
</List>
</div>
</template>
<script>
import List from './List.vue';
export default {
components: { List }
};
</script>
- 效果:
<ul> <li>苹果 (ID: 1)</li> <li>香蕉 (ID: 2)</li> </ul> - 解析:
:item="item"将子组件数据绑定到插槽。v-slot="{ item }"解构接收数据。
多参数传递
子组件
<template>
<div>
<slot :user="user" :active="isActive" />
</div>
</template>
<script>
import { ref, reactive } from 'vue';
export default {
setup() {
const user = reactive({ name: 'Alice', age: 25 });
const isActive = ref(true);
return { user, isActive };
}
};
</script>
父组件
<template>
<div>
<Child v-slot="{ user, active }">
<p>{{ user.name }} is {{ active ? 'active' : 'inactive' }}</p>
</Child>
</div>
</template>
<script>
import Child from './Child.vue';
export default {
components: { Child }
};
</script>
- 效果:显示“Alice is active”。
高级用法
1. 默认内容与条件渲染
子组件
<template>
<div>
<slot name="content" :items="items">
<p v-if="!items.length">暂无数据</p>
<ul v-else>
<li v-for="item in items" :key="item">{{ item }}</li>
</ul>
</slot>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const items = ref([]);
return { items };
}
};
</script>
父组件
<template>
<div>
<Child #content="{ items }">
<ul>
<li v-for="item in items" :key="item">{{ item.toUpperCase() }}</li>
</ul>
</Child>
</div>
</template>
- 效果:若
items为空,显示“暂无数据”;否则父组件自定义渲染。
2. 与动态组件结合
<template>
<div>
<button @click="currentComp = 'CompA'">CompA</button>
<button @click="currentComp = 'CompB'">CompB</button>
<component :is="currentComp" v-slot="{ text }">
<p>动态内容:{{ text }}</p>
</component>
</div>
</template>
<script>
import { ref } from 'vue';
import CompA from './CompA.vue';
import CompB from './CompB.vue';
export default {
components: { CompA, CompB },
setup() {
const currentComp = ref('CompA');
return { currentComp };
}
};
</script>
子组件(CompA.vue)
<template>
<slot :text="message" />
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const message = ref('来自 A');
return { message };
}
};
</script>
- 效果:切换组件时动态显示不同内容。
应用场景
- 布局组件:
- 使用具名插槽定义头部、主体、底部。
- 列表渲染:
- 作用域插槽自定义列表项样式。
- 通用组件:
- 如表格、模态框,内容由父组件决定。
示例:可复用表格
子组件(Table.vue)
<template>
<table>
<thead>
<tr>
<th v-for="col in columns" :key="col">{{ col }}</th>
</tr>
</thead>
<tbody>
<tr v-for="row in data" :key="row.id">
<slot name="row" :row="row">
<td v-for="col in columns" :key="col">{{ row[col] }}</td>
</slot>
</tr>
</tbody>
</table>
</template>
<script>
export default {
props: {
columns: Array,
data: Array
}
};
</script>
父组件
<template>
<div>
<Table :columns="columns" :data="items">
<template #row="{ row }">
<td>{{ row.name }}</td>
<td><button @click="edit(row.id)">编辑</button></td>
</template>
</Table>
</div>
</template>
<script>
import { reactive } from 'vue';
import Table from './Table.vue';
export default {
components: { Table },
setup() {
const columns = ['name', 'action'];
const items = reactive([
{ id: 1, name: '苹果' },
{ id: 2, name: '香蕉' }
]);
const edit = (id) => console.log('编辑:', id);
return { columns, items, edit };
}
};
</script>
- 效果:表格显示名称和操作按钮,点击触发事件。
注意事项
- 作用域隔离:
- 插槽内容在父组件作用域渲染,访问父组件数据。
- 性能:
- 过多插槽可能增加复杂度,合理规划。
- 命名冲突:
- 具名插槽确保唯一性。
总结
插槽和作用域插槽为 Vue 3 的组件化开发提供了极大的灵活性。默认插槽和具名插槽适合内容分发,作用域插槽则赋予父组件动态定制能力。通过这些机制,你可以打造高度可复用的组件。下一节将探索组件库开发,带你进入更高层次的实践!
