Vue3组件基础实战:Props、Emit、Slot综合案例
- Vue3组件基础实战:Props、Emit、Slot综合案例
- 1 前言
- 2 Vue组件通信基础知识点
- 2.1 Props(父向子传数据)
- 2.2 emit(子向父发送事件)
- 2.3 Slot插槽
- 2.4 深层组件通信
- 3 实战需求
- 4 完整代码实现
- 4.1 子组件 CardItem.vue(默认插槽)
- 4.2 子组件 BtnBox.vue(具名插槽)
- 4.3 父组件 Parent.vue
- 5 代码逻辑解析
- 6 优缺点分析
- 优点
- 不足
- 优化建议
- 7 拓展:不同场景通信选型总结
- 8 总结
Vue3组件基础实战:Props、Emit、Slot综合案例
1 前言
组件化是Vue框架的核心思想,把页面拆分成多个独立可复用的UI模块。组件之间需要进行数据通信,本文结合实战演示props父传子、emit子传父、slot插槽的使用,实现1个父组件,2个子组件综合案例,同时拓展深层组件通信方案。
2 Vue组件通信基础知识点
2.1 Props(父向子传数据)
父组件通过标签属性传递数据,子组件使用defineProps接收。
单向数据流原则:props为只读,子组件禁止直接修改props数据。
2.2 emit(子向父发送事件)
子组件使用defineEmits声明自定义事件,调用emit()触发事件并携带参数;父组件通过@事件名监听,接收子组件传回的数据。
2.3 Slot插槽
插槽实现父组件向子组件传入自定义模板内容。
- 默认插槽:直接写在子组件标签内部;
- 具名插槽:通过
name命名,父组件使用#插槽名使用; - 插槽支持设置后备默认内容。
2.4 深层组件通信
当组件嵌套层级很深,props逐层传递会产生props透传问题:
provide / inject:祖先组件向后代组件直接注入数据,跳过中间组件;Pinia:全局状态管理库,适合多组件、跨路由共享全局业务数据。
3 实战需求
- 创建父组件
Parent.vue,两个子组件CardItem.vue、BtnBox.vue; - 父组件使用props向两个子组件传递数据;
- 子组件通过emit事件,把操作结果回传给父组件;
- 使用默认插槽、具名插槽实现子组件内容自定义。
4 完整代码实现
4.1 子组件 CardItem.vue(默认插槽)
<template> <div class="card"> <!-- 默认插槽,父组件传入自定义内容 --> <slot></slot> <p>收到父组件数据:{{ info }}</p> <button @click="handleClick">提交反馈</button> </div> </template> <script setup> // props接收父组件传入数据 const props = defineProps({ info: { type: String, required: true } }) // 声明自定义事件 const emit = defineEmits(['sendResult']) // 点击按钮触发事件,把数据回传给父组件 const handleClick = () => { emit('sendResult', `【卡片组件】操作完成,收到内容:${props.info}`) } </script> <style scoped> .card { border: 1px solid #ccc; padding: 16px; margin:10px 0; border-radius:6px; } </style>4.2 子组件 BtnBox.vue(具名插槽)
<template> <div class="btn-box"> <p>编号:{{ id }}</p> <!-- 具名插槽 --> <slot name="btn"></slot> </div> </template> <script setup> const props = defineProps({ id: Number }) const emit = defineEmits(['onOperate']) const handleClick = () => { emit('onOperate', `【按钮组件】操作完成,编号:${props.id}`) } </script> <style scoped> .btn-box { border:1px solid #42b983; padding:16px; margin:10px 0; border-radius:6px; } </style>4.3 父组件 Parent.vue
<template> <h2>父组件</h2> <!-- CardItem:props传值,监听子组件sendResult事件,默认插槽填充内容 --> <CardItem :info="parentMsg" @sendResult="handleReceive"> <h3>父组件通过插槽自定义标题</h3> </CardItem> <!-- BtnBox:props传递id,#btn具名插槽,事件由子组件内部emit触发 --> <BtnBox :id="num" @onOperate="handleOperate"> <template #btn> <button>点击操作</button> </template> </BtnBox> <p style="margin-top:20px;">父组件接收子组件反馈:{{ res }}</p> </template> <script setup> import { ref } from 'vue' import CardItem from './CardItem.vue' import BtnBox from './BtnBox.vue' // 父组件状态数据 const parentMsg = ref('父组件传递的文本') const num = ref(1001) const res = ref('') // 接收CardItem回传的数据 const handleReceive = (val) => { res.value = val } // 接收BtnBox回传的数据 const handleOperate = (val) => { res.value = val } </script>5 代码逻辑解析
- props 父传子:父组件通过
:info、:id把数据下发,子组件defineProps接收并做类型校验,数据单向流动。 - emit 子传父:子组件触发自定义事件携带参数,父组件监听事件拿到子组件操作结果。
- 插槽使用
- CardItem 使用默认插槽,父组件标签中间书写内容,渲染到子组件 slot 位置;
- BtnBox 使用具名插槽
name="btn",父组件使用#btn传入自定义按钮模板。
- 严格遵守单向数据流,子组件只读取 props,不在子组件内部直接修改 props。
6 优缺点分析
优点
- 技术栈现代:使用 Vue3 +
<script setup>语法,紧跟当前主流开发模式。 - 结构清晰:目录导航完善,从概念到实战再到拓展,层次分明。
- 代码规范:代码块展示美观,变量命名语义化强,注释详细。
- 总结到位:提供了通信方式选型表,便于查阅记忆。
不足
- 缺视觉反馈:本文没有提供组件运行后的界面截图,读者无法直观看到插槽填充或样式渲染效果。
- 早期代码存在逻辑瑕疵:旧版本父组件中对同一个事件进行了双重绑定(组件 emit + 原生 click),容易造成逻辑混乱、重复触发的问题,现已修正。
- 非核心内容干扰:如果附带 CSDN 平台发布教程,会偏离技术分享主题,因此本文移除了发布相关操作内容。
优化建议
- 补充运行结果图:可以在本节之前,添加浏览器中的实际渲染效果图,标注 Props 传入文字、Slot 插入内容位置,增强直观性。
- 保证事件逻辑单一来源:事件触发逻辑统一放在子组件内部,父组件仅负责监听事件,避免多重绑定。
- 增加数据流示意图:添加箭头示意图,直观展示 props 向下传数据、emit 向上回传事件的流向,文章可读性更强。
7 拓展:不同场景通信选型总结
| 通信方式 | 适用场景 |
|---|---|
| props + emit | 直接父子组件通信 |
| v-model | 自定义组件双向绑定,是 props+emit 语法糖 |
| slot 插槽 | 父向子传递自定义模板 UI 内容 |
| provide / inject | 多层嵌套跨代传参,适合配置类数据 |
| Pinia | 全局多组件共享业务状态,中大型项目首选 |
8 总结
1. props 实现父向子传递数据,遵循单向数据流,子组件不能直接修改 props; 2. emit 实现子组件向父组件发送事件、回传数据; 3. slot 插槽实现组件 UI 内容灵活定制,分为默认插槽与具名插槽; 4. 简单父子组件使用 props+emit;多层嵌套可以用 provide/inject;全局状态推荐 Pinia; 5. 组件化开发核心:模块拆分、代码复用、低耦合,数据流向清晰便于维护调试。