当前位置: 首页 > news >正文

Vue3 v-slot 详解与示例

Vue3 v-slot 详解与示例

v-slot 是 Vue3 中用于处理插槽(Slots)的指令,它提供了更强大和灵活的插槽功能。下面通过详细的示例来展示各种用法。

🎯 基础概念

什么是插槽?

插槽是 Vue 组件的一个强大特性,它允许你在组件中预留位置,让父组件可以插入自定义内容。

📝 基本用法

1. 默认插槽

子组件 BaseLayout.vue

vue

<template><div class="container"><header><slot></slot> <!-- 默认插槽 --></header><main><p>这是主内容区域</p></main><footer><p>这是页脚</p></footer></div>
</template>

父组件使用

vue

<template><BaseLayout><!-- 插入到默认插槽的内容 --><h1>这是页面标题</h1><p>这是插入到header插槽的内容</p></BaseLayout>
</template><script setup>
import BaseLayout from './BaseLayout.vue'
</script>

2. 具名插槽

子组件 Card.vue

vue

<template><div class="card"><div class="card-header"><slot name="header"></slot> <!-- 具名插槽 --></div><div class="card-body"><slot name="body"></slot> <!-- 具名插槽 --></div><div class="card-footer"><slot name="footer"></slot> <!-- 具名插槽 --></div><!-- 默认插槽 --><slot></slot></div>
</template><style scoped>
.card {border: 1px solid #ddd;border-radius: 8px;padding: 16px;margin: 10px;
}
.card-header {font-weight: bold;margin-bottom: 10px;
}
.card-body {margin-bottom: 10px;
}
.card-footer {font-size: 0.8em;color: #666;
}
</style>

父组件使用具名插槽

vue

<template><Card><!-- 使用 v-slot 指令 --><template v-slot:header><h2>卡片标题</h2></template><template v-slot:body><p>这是卡片的主要内容区域</p><p>可以包含任何HTML内容</p></template><template v-slot:footer><span>卡片底部信息</span><button @click="handleClick">操作按钮</button></template></Card>
</template><script setup>
import Card from './Card.vue'const handleClick = () => {console.log('按钮被点击了')
}
</script>

🔥 v-slot 语法糖

Vue3 提供了 # 符号作为 v-slot: 的简写形式:

vue

<template><Card><!-- 简写语法 --><template #header><h2>简写语法示例</h2></template><template #body><p>使用 # 代替 v-slot:</p></template><template #footer><span>底部内容</span></template></Card>
</template>

💡 作用域插槽

作用域插槽允许子组件向插槽传递数据,父组件可以访问这些数据。

1. 基础作用域插槽

子组件 DataList.vue

vue

<template><div class="data-list"><ul><li v-for="item in items" :key="item.id"><!-- 向插槽传递数据 --><slot :item="item" :index="index"></slot></li></ul></div>
</template><script setup>
defineProps({items: {type: Array,default: () => []}
})
</script>

父组件使用作用域插槽

vue

<template><DataList :items="userList"><!-- 接收子组件传递的数据 --><template v-slot:default="slotProps"><div class="user-item"><span>{{ slotProps.index + 1 }}. </span><strong>{{ slotProps.item.name }}</strong><em>({{ slotProps.item.age }} 岁)</em></div></template></DataList>
</template><script setup>
import { ref } from 'vue'
import DataList from './DataList.vue'const userList = ref([{ id: 1, name: '张三', age: 25 },{ id: 2, name: '李四', age: 30 },{ id: 3, name: '王五', age: 28 }
])
</script><style scoped>
.user-item {padding: 8px;margin: 4px 0;background-color: #f5f5f5;border-radius: 4px;
}
</style>

2. 解构作用域插槽参数

vue

<template><DataList :items="userList"><!-- 使用解构语法 --><template #default="{ item, index }"><div class="user-item" :class="{ active: index % 2 === 0 }"><span>{{ index + 1 }}. </span><strong>{{ item.name }}</strong><span class="age">{{ item.age }} 岁</span></div></template></DataList>
</template>

🎭 具名作用域插槽

子组件 UserCard.vue

vue

<template><div class="user-card"><!-- 具名作用域插槽 --><slot name="avatar" :user="user" :size="avatarSize"></slot><div class="user-info"><slot name="name" :user="user"></slot><slot name="details" :user="user"></slot></div><div class="actions"><slot name="actions" :user="user" :onEdit="handleEdit"></slot></div></div>
</template><script setup>
import { ref } from 'vue'const props = defineProps({user: {type: Object,required: true}
})const avatarSize = ref('medium')const handleEdit = () => {console.log('编辑用户:', props.user.name)
}
</script><style scoped>
.user-card {border: 1px solid #e0e0e0;border-radius: 8px;padding: 16px;margin: 10px;display: flex;align-items: center;gap: 16px;
}
.user-info {flex: 1;
}
.actions {display: flex;gap: 8px;
}
</style>

父组件使用具名作用域插槽

vue

<template><UserCard :user="currentUser"><!-- 具名作用域插槽 --><template #avatar="{ user, size }"><div class="avatar" :class="size"><img :src="user.avatar" :alt="user.name" /></div></template><template #name="{ user }"><h3>{{ user.name }}</h3></template><template #details="{ user }"><p>邮箱: {{ user.email }}</p><p>角色: {{ user.role }}</p></template><template #actions="{ user, onEdit }"><button @click="onEdit">编辑</button><button @click="deleteUser(user.id)">删除</button></template></UserCard>
</template><script setup>
import { ref } from 'vue'
import UserCard from './UserCard.vue'const currentUser = ref({id: 1,name: '张三',email: 'zhangsan@example.com',role: '管理员',avatar: '/avatars/zhangsan.jpg'
})const deleteUser = (userId) => {console.log('删除用户:', userId)
}
</script><style scoped>
.avatar {width: 50px;height: 50px;border-radius: 50%;overflow: hidden;
}
.avatar.medium {width: 50px;height: 50px;
}
.avatar img {width: 100%;height: 100%;object-fit: cover;
}
</style>

🔄 动态插槽名

vue

<template><DynamicComponent><!-- 动态插槽名 --><template v-slot:[dynamicSlotName]><p>这是动态插槽的内容</p></template><!-- 使用计算属性 --><template #[computedSlotName]><p>这是计算属性决定的插槽内容</p></template></DynamicComponent>
</template><script setup>
import { ref, computed } from 'vue'const dynamicSlotName = ref('header')
const slotType = ref('primary')const computedSlotName = computed(() => {return `${slotType.value}-content`
})
</script>

📊 高级示例:数据表格组件

子组件 DataTable.vue

vue

<template><div class="data-table"><table><thead><tr><!-- 动态表头 --><th v-for="column in columns" :key="column.key"><slot name="header" :column="column">{{ column.title }}</slot></th></tr></thead><tbody><!-- 动态数据行 --><tr v-for="(row, index) in data" :key="row.id"><td v-for="column in columns" :key="column.key"><slot name="cell" :value="row[column.key]" :row="row" :column="column":index="index">{{ row[column.key] }}</slot></td></tr></tbody></table><!-- 空状态插槽 --><div v-if="data.length === 0" class="empty-state"><slot name="empty"><p>暂无数据</p></slot></div></div>
</template><script setup>
defineProps({columns: {type: Array,default: () => []},data: {type: Array,default: () => []}
})
</script><style scoped>
.data-table {width: 100%;
}
table {width: 100%;border-collapse: collapse;
}
th, td {border: 1px solid #ddd;padding: 8px;text-align: left;
}
th {background-color: #f5f5f5;
}
.empty-state {text-align: center;padding: 40px;color: #666;
}
</style>

父组件使用数据表格

vue

<template><DataTable :columns="columns" :data="users"><!-- 自定义表头 --><template #header="{ column }"><span class="header-cell">{{ column.title }}<span v-if="column.sortable" class="sort-icon">↕️</span></span></template><!-- 自定义单元格内容 --><template #cell="{ value, row, column }"><template v-if="column.key === 'avatar'"><img :src="value" :alt="row.name" class="avatar" /></template><template v-else-if="column.key === 'status'"><span :class="['status', value]">{{ getStatusText(value) }}</span></template><template v-else-if="column.key === 'actions'"><button @click="editUser(row)">编辑</button><button @click="deleteUser(row.id)">删除</button></template><template v-else>{{ value }}</template></template><!-- 自定义空状态 --><template #empty><div class="custom-empty"><p>📊 没有找到任何用户数据</p><button @click="loadUsers">重新加载</button></div></template></DataTable>
</template><script setup>
import { ref } from 'vue'
import DataTable from './DataTable.vue'const columns = ref([{ key: 'id', title: 'ID', sortable: true },{ key: 'name', title: '姓名', sortable: true },{ key: 'avatar', title: '头像' },{ key: 'email', title: '邮箱' },{ key: 'status', title: '状态' },{ key: 'actions', title: '操作' }
])const users = ref([{id: 1,name: '张三',avatar: '/avatars/1.jpg',email: 'zhangsan@example.com',status: 'active'},{id: 2,name: '李四',avatar: '/avatars/2.jpg',email: 'lisi@example.com',status: 'inactive'}
])const getStatusText = (status) => {const statusMap = {active: '活跃',inactive: '非活跃'}return statusMap[status] || status
}const editUser = (user) => {console.log('编辑用户:', user)
}const deleteUser = (userId) => {console.log('删除用户:', userId)
}const loadUsers = () => {console.log('重新加载用户数据')
}
</script><style scoped>
.header-cell {display: flex;align-items: center;gap: 4px;
}
.sort-icon {font-size: 12px;
}
.avatar {width: 30px;height: 30px;border-radius: 50%;
}
.status {padding: 2px 8px;border-radius: 4px;font-size: 12px;
}
.status.active {background-color: #e8f5e8;color: #2e7d32;
}
.status.inactive {background-color: #ffebee;color: #c62828;
}
.custom-empty {text-align: center;padding: 40px;
}
</style>

💡 最佳实践与技巧

1. 提供合理的默认内容

vue

<!-- 子组件 -->
<template><div class="notification"><slot name="icon"><!-- 默认图标 --><span class="default-icon">💡</span></slot><div class="content"><slot name="title"><h3>默认标题</h3></slot><slot name="message"><p>默认消息内容</p></slot></div></div>
</template>

2. 使用条件插槽

vue

<template><Modal :show="showModal"><!-- 条件性渲染插槽内容 --><template #header v-if="hasCustomHeader"><CustomHeader :title="modalTitle" /></template><template #body><div v-if="isLoading"><slot name="loading"><p>加载中...</p></slot></div><div v-else><slot name="content"></slot></div></template></Modal>
</template>

3. 插槽性能优化

vue

<template><VirtualList :items="largeDataSet"><!-- 使用 v-memo 优化大量插槽内容 --><template #item="{ item, index }"><div v-memo="[item.id]"><slot name="item-content" :item="item" :index="index">{{ item.name }}</slot></div></template></VirtualList>
</template>

🎯 总结

Vue3 的 v-slot 提供了强大的插槽功能:

特性语法用途
默认插槽<slot>基本内容插入
具名插槽v-slot:name 或 #name多个插槽区分
作用域插槽v-slot="props"子向父传递数据
动态插槽v-slot:[dynamicName]动态决定插槽名
解构语法#default="{ prop }"简化数据访问

关键优势

  • 更好的类型推断(配合 TypeScript)

  • 更清晰的语法(特别是简写形式)

  • 更强的灵活性(动态插槽、作用域插槽)

  • 更好的性能(优化的渲染机制)

掌握这些插槽技术可以让你创建出高度可复用和灵活的组件,大大提升开发效率。

http://www.dtcms.com/a/406581.html

相关文章:

  • Agno 架构介绍:高性 Multi-agent 系统框架深度解析
  • 哪里有南宁网站建设天河区建设和水务局网站
  • Cadence Allegro 电子设计 快问快答--03.OrCAD颜色在哪里设置?
  • 自己做的网站怎么上排行榜设计广告专业制作
  • 网站页面下沉的特效代码山西seo推广系统
  • S7-200 SMART GET/PUT 指令深度解析:从参数到故障排查(S7 协议的客户端 - 服务器通信)下
  • 做国外网站翻译中国小说赚钱外贸推广软件有哪些
  • 二次封装科技风大屏element-ui弹窗
  • 【JavaScript 性能优化实战】第六篇:性能监控与自动化优化
  • 沃地泰双奖加冕2025农机大奖,以创新科技赋能智慧农业
  • Spring + Spring MVC + MyBatis
  • 酒店 深圳 网站制作如何找外包网站来做
  • 雪碧图动画实例
  • 总结之webpack
  • 义乌市建设局网站河南建设工程信息网官网洛阳至信阳省道
  • 实时云渲染云推流平台配置Redis、MySQL端口,解决中间件端口冲突
  • 网站建设会议记录增城住房和建设局网站
  • shell编程:sed - 流编辑器(6)
  • Kubernetes ConfigMap 深度解析:配置管理的核心实践Kubernetes Secret 深度解析:敏感配置的安全管理实践
  • 如何实现多人协同文档编辑器
  • 备案ip 查询网站wordpress 分类全文
  • 桂林市建设局网站网站建设招聘
  • 5G x 工业应用:探索德承工控机在5G工业应用中所扮演的关键角色
  • 队列+宽搜(BFS)-662.二叉树最大宽度-力扣(LeetCode)
  • 【C++实战㊷】C++ 原型模式实战:从概念到高效应用
  • MCP 安全“体检” | AI 驱动的 MCP 安全扫描系统
  • 股票跟单网站开发长沙网站建设推广服务
  • 谷城网站制作wordpress给用户推送消息
  • (16)ASP.NET Core2.2 通用主机(HostBuilder)
  • .NET Core报错解决【无废话上操作】