Vue3 中 props 与 $emit 的使用及 defineProps 与 defineEmits 的区别详解
一、基本概念
1. 父子组件通信原则
Vue 遵循 单向数据流 原则:父组件通过 props
向子组件传递数据;子组件若需反馈信息,则使用事件系统($emit
)通知父组
数据流向:父 → 子(via
props
),子 → 父(viaemit
)
二、传统 Options API 写法(Vue3 兼容)
父组件 App.vue
<template><div><h2>父组件</h2><p>收到子组件消息:{{ messageFromChild }}</p><ChildComponent :title="parentTitle" @send-message="handleMessage"/></div>
</template><script>
import ChildComponent from './ChildComponent.vue';export default {components: { ChildComponent },data() {return {parentTitle: '来自父组件的标题',messageFromChild: ''};},methods: {handleMessage(msg) {this.messageFromChild = msg;}}
};
</script>
子组件 ChildComponent.vue
<template><div><h3>{{ title }}</h3><button @click="sendToParent">发送消息给父组件</button></div>
</template><script>
export default {props: ['title'],emits: ['send-message'], // 显式声明触发的事件名methods: {sendToParent() {this.$emit('send-message', '你好,我是子组件!');}}
};
</script>
✅ 使用
props
接收父级数据,使用this.$emit
触发事件。
三、Composition API + <script setup>
新写法
<script setup>
是 Vue3 提供的编译时语法糖,让组合式 API 更简洁。
父组件(App.vue)
<template><div><h2>父组件(setup 版)</h2><p>收到子组件消息:{{ messageFromChild }}</p><ChildComponent :title="parentTitle" @send-message="handleMessage"/></div>
</template><script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';const parentTitle = ref('来自 setup 父组件的标题');
const messageFromChild = ref('');const handleMessage = (msg) => {messageFromChild.value = msg;
};
</script>
子组件(ChildComponent.vue)— 使用 defineProps
和 defineEmits
<template><div><h3>{{ title }}</h3><button @click="sendToParent">发送消息给父组件</button></div>
</template><script setup>
// 定义接收的 props
const props = defineProps({title: {type: String,required: true}
});// 定义可触发的事件
const emit = defineEmits(['send-message']);// 发送事件到父组件
const sendToParent = () => {emit('send-message', '这是通过 defineEmits 发送的消息!');
};
</script>
🔥 注意:
defineProps
和defineEmits
是 宏函数(macro),不能被解构或动态调用。
四、高级用法:类型安全(配合 TypeScript)
<script setup lang="ts">
interface Props {title: string;count?: number;
}// 类型化 defineProps
const props = defineProps<Props>();// 类型化 defineEmits
const emit = defineEmits<{(e: 'send-message', msg: string): void;(e: 'update-count', value: number): void;
}>();const sendToParent = () => {emit('send-message', '类型安全的消息');emit('update-count', props.count || 0);
};
</script>
⚡️ TypeScript 下自动提示 + 类型检查,极大提升开发体验!
五、常见误区与注意事项
问题 | 正确做法 |
---|---|
❌ 尝试解构 defineProps 返回值 | const { title } = defineProps(...) ❌ 不支持 |
✅ 应始终通过 props.xxx 访问 | props.title ✅ 响应式保留 |
❌ 在条件语句中调用 defineEmits | 必须顶层调用 |
✅ defineProps 和 defineEmits 不需要导入 | 编译时自动识别 |
六、通信流程图(文字描述 + 图表示意)
📊 图表示意(文本模拟)
箭头方向清晰体现 数据单向流动 + 事件反向通知
七、多个例子实战演示
示例 1:计数器增减控制(父控子操作)
父组件
<ChildCounter v-model:count="count"/>
子组件
<script setup>
defineProps(['count']);
defineEmits(['update:count']);
</script>
<template><div><span>当前值:{{ count }}</span><button @click="$emit('update:count', count + 1)">+</button><button @click="$emit('update:count', count - 1)">-</button></div>
</script>
实现了
.sync
模式的替代方案,使用v-model:count
绑定。
示例 2:表单输入双向同步(类似 v-model)
父组件
<FormInput v-model="inputValue"/>
子组件 FormInput.vue
<script setup>
const props = defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);const updateValue = (e) => {emit('update:modelValue', e.target.value);
};
</script><template><input :value="modelValue" @input="updateValue" />
</template>
相当于实现了自定义
v-model
行为。
示例 3:异步完成通知
子组件完成异步任务后通知父组件更新状态
<script setup>
const emit = defineEmits(['done']);const asyncTask = async () => {await new Promise(resolve => setTimeout(resolve, 1000));emit('done', { success: true, time: Date.now() });
};
</script>
父组件监听
<AsyncComponent @done="onDone"/>
总结对比表
对比项 | Options API (props , $emit ) | <script setup> (defineProps , defineEmits ) |
---|---|---|
是否需要 export default | 是 | 否(自动导出) |
props 定义位置 | props: {} 选项内 | defineProps({...}) 宏调用 |
emit 调用方式 | this.$emit('event') | emit('event') (函数调用) |
是否支持类型推导 | 否(JS) / 有限(TS 需额外配置) | ✅ 支持 TS 泛型推导 |
是否响应式解构 | ❌ 解构会失去响应性 | ❌ 不允许解构 props |
开发效率 | 一般 | 更高(少模板代码) |
知识点
单向数据流:父组件通过
props
传值给子组件,子组件不应直接修改props
,保持数据流向清晰。事件发射机制:子组件通过
$emit
或emit
触发事件,父组件通过@event
监听并处理。宏函数特性:
defineProps
和defineEmits
是编译宏,只能在<script setup>
顶层使用,不需引入。