在 Vue 3 中,可以通过 defineExpose 编译器宏显式暴露组件内部的属性或方法,使父组件能够通过模板引用(ref)直接调用子组件的 API。以下是具体实现方式:
1. 子组件暴露 API
在子组件中使用 <script setup> 语法时,直接通过 defineExpose 暴露需要对外开放的属性和方法:
<!-- ChildComponent.vue -->
<template>
<div>当前计数: {{ count }}</div>
</template>
<script setup>
import { ref } from 'vue'
// 需要暴露出去的的内部状态
const count = ref(0)
// 需要暴露的方法
const increment = () => {
count.value++
}
// 暴露给父组件的 API
defineExpose({
count,
increment
})
</script>
2. 父组件调用子组件 API
在父组件中通过 ref 获取子组件实例,并调用其暴露的 API:
<template>
<ChildComponent ref="childRef" />
<button @click="handleClick">增加</button>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'
// 获取子组件的引用
const childRef = ref(null)
// 调用子组件里的 increment() 方法
const handleClick = () => {
childRef.value.increment()
console.log('子组件的 count:', childRef.value.count)
}
</script>
关键注意事项
- 暴露内容控制:
- 只有通过
defineExpose显式暴露的属性和方法才能被外部访问。 - 未暴露的内容即使存在于组件实例中,父组件也无法直接调用。
- 只有通过
- 类型安全:
- 建议使用 TypeScript 接口明确定义暴露的 API 类型:
interface ChildComponentAPI {
count: number
increment: () => void
}
defineExpose<ChildComponentAPI>({ count, increment })
- 响应式更新:
- 如果暴露的是响应式对象(如
ref或reactive),父组件获取的将是响应式数据的引用,会自动保持同步。
- 如果暴露的是响应式对象(如
- 生命周期限制:
- 只能在组件挂载后(如
onMounted钩子)调用子组件 API,确保子组件已完成初始化。
- 只能在组件挂载后(如
**替代方案:使用 **provide/inject
如果需要在深层嵌套的组件间共享 API,可以考虑组合式 API 的 provide/inject:
<!-- 祖先组件 -->
<script setup>
import { provide } from 'vue'
const sharedMethod = () => { /* ... */ }
provide('sharedMethod', sharedMethod)
</script>
<!-- 后代组件 -->
<script setup>
import { inject } from 'vue'
const sharedMethod = inject('sharedMethod')
</script>
选择 defineExpose 还是 provide/inject 取决于具体场景:前者适合父子直接通信,后者适合跨层级通信。