人人都会AI编程

9.4 父组件调用子组件方法:ref 引用与 defineExpose

更新时间:2026-07-09

在 Vue 的组件通信模型中,数据流通常是自上而下的(Props 向下,Events 向上)。但有时父组件确实需要直接触发子组件内部的某个行为——例如重置表单、打开弹窗、调用子组件封装好的数据刷新方法。这时就需要用到 ref 引用defineExpose

基本用法:ref 获取子组件实例

Vue 允许父组件通过 ref 属性标记子组件,然后在 <script setup> 中声明一个同名的 ref 变量,Vue 会自动把子组件的暴露出来的内容挂载到这个 ref 上。

<!-- 子组件 ChildForm.vue -->
<template>
  <form>
    <input v-model="formData.name" />
    <input v-model="formData.email" />
  </form>
</template>

<script setup>
import { reactive } from 'vue'

const formData = reactive({ name: '', email: '' })

function resetForm() {
  formData.name = ''
  formData.email = ''
}

// 明确向父组件暴露 resetForm 方法
defineExpose({ resetForm })
</script>
<!-- 父组件 Parent.vue -->
<template>
  <ChildForm ref="formRef" />
  <button @click="handleReset">重置表单</button>
</template>

<script setup>
import { ref } from 'vue'
import ChildForm from './ChildForm.vue'

// 变量名必须与模板中的 ref 属性一致
const formRef = ref(null)

function handleReset() {
  // 调用子组件暴露的方法
  formRef.value.resetForm()
}
</script>

要点:

  • ref 变量名必须与模板中声明的 ref 属性值完全一致,Vue 会在模板编译时自动完成绑定。
  • 访问子组件暴露的内容需要使用 .value(因为 formRef 本身是一个 ref 包裹的对象)。
  • 默认情况下,<script setup> 组件内部的变量、方法都是私有的,父组件无法直接访问。

defineExpose:控制暴露范围

如果不使用 defineExpose,父组件通过 ref 只能拿到一个空对象。这是因为在 <script setup> 模式下,所有顶级绑定(数据、方法、计算属性)默认对外不可见。只有明确通过 defineExpose 导出的内容,才会出现在父组件拿到的 ref 实例上。

<script setup>
import { ref } from 'vue'

const internalState = ref('secret')
const sharedData = ref('public')

function doSomething() { ... }
function callableMethod() { ... }

defineExpose({
  sharedData,
  callableMethod
})
</script>

这样父组件可以访问 sharedData 和调用 callableMethod(),但无法触及 internalStatedoSomething()。这是一种最小权限原则:只暴露父组件真正需要的东西,避免内部实现被意外依赖,提升封装性。

真实场景举例

  1. 表单重置/校验

父组件中包含多个步骤的表单,每个步骤是一个子组件。点击“提交”时,父组件调用子组件的 validate() 方法进行校验;点击“重置”时,调用子组件的 reset() 方法清空所有输入。

  1. 弹窗/抽屉控制

子组件是一个全局的确认弹窗,父组件通过 ref 调用它的 show()hide()openWithData(data) 方法,而不是在 Props 中传递一个复杂的 visible 属性并费力同步状态。

  1. 图表/地图实例调用

子组件封装了第三方可视化库,父组件可能需要调用图表实例的 updateChart()exportImage() 方法,子组件通过 defineExpose 将图表实例的引用暴露出去。

注意事项与误区

  • 不要过度使用

如果可以通过 Props 和 Events 实现通信,优先使用那种方式。ref 调用子组件方法会让父子耦合变紧,违背组件自闭环设计的初衷。只在“必须触发内部行为”时使用。

  • 确保子组件已挂载

onMounted 之前,formRef.value 可能为 null。如果你在 setup 同步代码中直接调用子组件方法,很可能会报错。通常将调用放在事件回调里,或者在 onMounted 中安全访问。

  • 避免直接修改子组件数据

通过 ref 暴露响应式数据给父组件,可以让父组件直接修改它。但这会破坏单向数据流,导致状态变更难以追踪。更好的做法是暴露方法,由子组件自己维护数据的修改。

  • $refs 的差异(选项式 API)

在选项式 API 中,父组件通过 this.$refs.xxx 获取子组件实例,默认可以访问子组件的所有 datamethods。而 <script setup> + defineExpose 提供了更严格的封装,这是组合式 API 更安全的实践。

简单总结:当父子组件之间需要“父调子”时,给子组件加 ref,子组件用 defineExpose 开门,父组件就能调用指定的方法。这个机制简单直接,但要控制好暴露粒度,避免滥用。