插槽(Slot)允许父组件向子组件传递模板内容。
<!-- Button.vue -->
<template>
<button class="custom-button">
<slot>默认按钮文本</slot>
</button>
</template>
<!-- 使用 -->
<template>
<div>
<Button>点击我</Button>
<Button>提交</Button>
<Button /> <!-- 显示默认文本 -->
</div>
</template>
使用多个插槽时,需要给插槽命名。
<!-- Card.vue -->
<template>
<div class="card">
<div class="card-header">
<slot name="header">默认标题</slot>
</div>
<div class="card-body">
<slot>默认内容</slot>
</div>
<div class="card-footer">
<slot name="footer">默认页脚</slot>
</div>
</div>
</template>
<!-- 使用 -->
<template>
<Card>
<template #header>
<h3>卡片标题</h3>
</template>
<p>这是卡片的主要内容</p>
<template #footer>
<button>确定</button>
<button>取消</button>
</template>
</Card>
</template>
子组件可以向插槽传递数据,父组件可以访问这些数据。
<!-- List.vue -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item" :index="item.id">
{{ item.name }}
</slot>
</li>
</ul>
</template>
<script setup>
defineProps(['items'])
</script>
<!-- 使用 -->
<template>
<List :items="users">
<template #default="{ item, index }">
<strong>{{ index }}.</strong> {{ item.name }} - {{ item.age }}岁
</template>
</List>
</template>
<script setup>
import { ref } from 'vue'
import List from './List.vue'
const users = ref([
{ id: 1, name: '张三', age: 25 },
{ id: 2, name: '李四', age: 30 }
])
</script>
<template>
<Card>
<template #[dynamicSlotName]>
动态插槽内容
</template>
</Card>
</template>
<script setup>
import { ref } from 'vue'
const dynamicSlotName = ref('header')
</script>
使用component标签和is属性动态切换组件。
<template>
<div>
<button @click="currentTab = 'Home'">首页</button>
<button @click="currentTab = 'About'">关于</button>
<button @click="currentTab = 'Contact'">联系</button>
<component :is="currentTab" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import Home from './Home.vue'
import About from './About.vue'
import Contact from './Contact.vue'
const currentTab = ref('Home')
</script>
使用keep-alive包裹动态组件,保持组件状态。
<template>
<div>
<button @click="currentView = 'ComponentA'">A</button>
<button @click="currentView = 'ComponentB'">B</button>
<keep-alive>
<component :is="currentView" />
</keep-alive>
</div>
</template>
<script setup>
import { ref } from 'vue'
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'
const currentView = ref('ComponentA')
</script>
<template>
<div>
<!-- 只缓存name为a或b的组件 -->
<keep-alive include="a,b">
<component :is="view" />
</keep-alive>
<!-- 不缓存name为c的组件 -->
<keep-alive exclude="c">
<component :is="view" />
</keep-alive>
<!-- 最多缓存10个组件 -->
<keep-alive :max="10">
<component :is="view" />
</keep-alive>
</div>
</template>
<template>
<div>缓存组件</div>
</template>
<script setup>
import { onActivated, onDeactivated } from 'vue'
// 组件被激活时调用
onActivated(() => {
console.log('组件激活')
})
// 组件被停用时调用
onDeactivated(() => {
console.log('组件停用')
})
</script>
使用defineAsyncComponent定义异步组件,实现代码分割和懒加载。
<template>
<div>
<AsyncComponent />
</div>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
// 简单用法
const AsyncComponent = defineAsyncComponent(() =>
import('./components/HeavyComponent.vue')
)
// 高级用法
const AsyncComponentAdvanced = defineAsyncComponent({
loader: () => import('./components/HeavyComponent.vue'),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000
})
</script>
Suspense用于协调异步依赖的加载状态。
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>加载中...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
)
</script>
<!-- Tabs.vue -->
<template>
<div class="tabs">
<div class="tabs-header">
<button
v-for="tab in tabs"
:key="tab"
:class="{ active: activeTab === tab }"
@click="activeTab = tab"
>
{{ tab }}
</button>
</div>
<div class="tabs-content">
<slot :name="activeTab"></slot>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
defineProps(['tabs'])
const activeTab = ref('tab1')
</script>
<style scoped>
.tabs-header button {
padding: 10px 20px;
border: none;
background: #f0f0f0;
cursor: pointer;
}
.tabs-header button.active {
background: #42b883;
color: white;
}
.tabs-content {
padding: 20px;
border: 1px solid #ddd;
}
</style>
<!-- 使用 -->
<template>
<Tabs :tabs="['tab1', 'tab2', 'tab3']">
<template #tab1>
<h3>标签页1内容</h3>
<p>这是第一个标签页</p>
</template>
<template #tab2>
<h3>标签页2内容</h3>
<p>这是第二个标签页</p>
</template>
<template #tab3>
<h3>标签页3内容</h3>
<p>这是第三个标签页</p>
</template>
</Tabs>
</template>
<!-- DataList.vue -->
<template>
<div class="data-list">
<div v-if="loading">
<slot name="loading">加载中...</slot>
</div>
<div v-else-if="error">
<slot name="error" :error="error">加载失败</slot>
</div>
<div v-else-if="items.length === 0">
<slot name="empty">暂无数据</slot>
</div>
<div v-else>
<div v-for="item in items" :key="item.id">
<slot :item="item">{{ item }}</slot>
</div>
</div>
</div>
</template>
<script setup>
defineProps(['items', 'loading', 'error'])
</script>
<!-- 使用 -->
<template>
<DataList :items="users" :loading="isLoading" :error="error">
<template #default="{ item }">
<div class="user-card">
<h4>{{ item.name }}</h4>
<p>{{ item.email }}</p>
</div>
</template>
<template #loading>
<div class="spinner">正在加载用户数据...</div>
</template>
<template #empty>
<div>没有找到用户</div>
</template>
<template #error="{ error }">
<div class="error">错误: {{ error.message }}</div>
</template>
</DataList>
</template>