在 Vue 项目中,子组件调用父组件方法是一个常见的通信需求。Vue 提供了多种方式实现这一点,最主流的有:
- 通过
props传递回调函数 - 通过
$emit触发自定义事件(推荐) - 使用插槽(Slots)进行内容与逻辑解耦
下面将分别给出 不使用插槽 和 使用插槽 的完整示例,并对比它们的区别。
✅ 一、不使用插槽:子组件调用父组件方法(两种方式)
方式1:通过 $emit(官方推荐)
子组件触发事件 → 父组件监听并执行方法
父组件 Parent.vue
vue
<template>
<div>
<h2>父组件</h2>
<p>当前计数:{{ count }}</p>
<Child @increment="handleIncrement" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const count = ref(0)
const handleIncrement = (step) => {
count.value += step
}
</script>
子组件 Child.vue
vue
<template>
<button @click="sendToParent">点击 +2(通过 $emit)</button>
</template>
<script setup>
const emit = defineEmits(['increment'])
const sendToParent = () => {
emit('increment', 2) // 向父组件传值 2
}
</script>
方式2:通过 props 传递函数
父组件把方法作为 prop 传给子组件 → 子组件直接调用
父组件 Parent.vue
vue
<template>
<div>
<h2>父组件</h2>
<p>当前计数:{{ count }}</p>
<Child :on-increment="handleIncrement" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child2.vue'
const count = ref(0)
const handleIncrement = (step) => {
count.value += step
}
</script>
子组件 Child2.vue
vue
<template>
<button @click="callParent">点击 +3(通过 props 回调)</button>
</template>
<script setup>
const props = defineProps({
onIncrement: {
type: Function,
required: true
}
})
const callParent = () => {
props.onIncrement(3) // 直接调用父组件方法并传参
}
</script>
✅ 二、使用插槽(Slots)的方式
插槽本身 不直接用于"调用父组件方法" ,但可以 让父组件在插槽内容中绑定自己的方法,从而实现更灵活的交互。
场景:模态框(Modal)组件,按钮由父组件提供
子组件 Modal.vue(使用默认插槽)
vue
<template>
<div class="modal" v-if="visible">
<div class="content">
<slot></slot> <!-- 父组件传入的内容 -->
</div>
<button @click="close">关闭</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const visible = ref(true)
const close = () => {
visible.value = false
}
// 如果需要父组件控制显隐,也可暴露方法
defineExpose({ show: () => visible.value = true })
</script>
父组件 Parent.vue
vue
<template>
<div>
<h2>使用插槽的模态框</h2>
<Modal>
<!-- 插槽内容由父组件定义,可自由绑定自己的方法 -->
<p>这是模态框内容</p>
<button @click="parentMethod">点击调用父组件方法!</button>
</Modal>
</div>
</template>
<script setup>
import Modal from './Modal.vue'
const parentMethod = () => {
alert('我是父组件的方法!')
}
</script>
✅ 此时,"调用父组件方法"的逻辑完全由父组件在插槽中控制,子组件无需知道具体行为。
🔍 三、对比:使用插槽 vs 不使用插槽
| 对比维度 | 不使用插槽(props / emit) | 使用插槽 |
|---|---|---|
| 职责分离 | 子组件需明确知道要调用哪个方法或事件 | 子组件只提供"容器",逻辑由父组件注入 |
| 灵活性 | 较低:交互逻辑写死在子组件中 | 极高:父组件可任意定制插槽内容和行为 |
| 复用性 | 适合固定行为的组件(如按钮、输入框) | 适合结构固定但内容多变的组件(如卡片、弹窗、表格行) |
| 代码量 | 简单场景代码少 | 需要 <slot> 标签,但减少 props 类型定义 |
| 性能 | 正常 | 更优(插槽内容按需渲染,避免无谓 props 传递) |
| 适用场景 | 表单提交、简单交互 | UI 容器类组件(Modal、Card、List、Layout) |
✅ 总结建议
- **优先使用 **
$emit实现子 → 父通信(符合 Vue 单向数据流理念)。 - 当子组件是"UI 容器" (如弹窗、卡片、布局),且内容/行为高度可变时,强烈推荐使用插槽。
- 避免滥用
props传函数,除非你明确需要类似 React 的回调模式(如高阶组件)。 - 不要用
$refs调用父方法(反模式,破坏封装性)。