Vue3 探秘:defineExpose 如何暴露方法给父组件及原理详解

1,560 阅读5分钟

一、引言

image.png

在 Vue3 的开发中,defineExpose起着至关重要的作用。它主要用于子组件向父组件暴露属性和方法,极大地促进了组件间的通信和交互。

在 Vue3 的组件化开发模式下,组件的封装性和可维护性至关重要。而defineExpose为我们提供了一种灵活控制组件对外暴露内容的方式。在默认情况下,使用

同时,defineExpose也方便了父组件与子组件进行交互。父组件可以通过给子组件添加ref属性,然后在父组件中使用这个ref来访问子组件暴露出来的属性和方法。这样可以实现父组件对子组件的特定操作和数据获取,促进了组件之间的通信和协作。

例如,在一个复杂的应用中,可能有多个子组件,每个子组件都有自己特定的功能和数据。通过defineExpose,我们可以精确地控制哪些数据和方法可以被父组件访问,避免了不必要的混乱和错误。

总之,defineExpose在 Vue3 中是一个非常重要的工具,它为我们提供了一种更加灵活、可控的组件间通信方式,增强了组件的封装性和可维护性。

二、defineExpose 的基本用法

image.png

  1. 在子组件中定义需要暴露的属性和方法,并使用 defineExpose 进行暴露。例如:
    • 子组件代码示例:
<template>
  <div>子组件内容</div>
</template>
<script setup>
import { reactive } from 'vue';
let info = reactive({ name: '子组件名称', age: 20 });
const isChild = () => {
  console.log('我是子组件');
};
// 暴露 info 数据和 isChild 方法
defineExpose({ info, isChild });
</script>

父组件通过 ref 获取子组件实例,并调用暴露的属性和方法:

<template>
  <div>
    <button @click="edit">父组件的按钮</button>
    <Child ref="childComponent" />
  </div>
</template>
<script setup>
import Child from './Child.vue';
import { ref } from 'vue';
const childComponent = ref(null);
// 在适当的时候调用子组件暴露的方法和属性
childComponent.value.isChild();
console.log(childComponent.value.info);
</script>

2. 说明 defineExpose 的语法格式:defineExpose({ 数据, 数据, 方法 });

子组件代码示例,展示如何定义和暴露属性和方法:

<template>
  <div>子组件内容</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(100);
const updateCount = function () {
  count.value++;
};
defineExpose({ count, str: 'vue3', updateCount });
</script>
    • 父组件通过 ref 获取子组件实例,并调用暴露的属性和方法:
<template>
  <div>
    <Child ref="child" />
    <button @click="update">点击修改子值</button>
  </div>
</template>
<script setup>
import Child from './Child.vue';
import { ref } from 'vue';
const child = ref(null);
const update = function () {
  child.value.updateCount();
};
onMounted(() => {
  console.log(child.value);
});
</script>

三、父组件调用子组件暴露的方法

  1. 父组件中使用 ref 属性获取子组件实例。在父组件的代码结构中,通常会通过import引入子组件,然后在模板中使用子组件并绑定ref属性。例如:
<template>
  <div>
    <Child ref="childComponent" />
  </div>
</template>
<script setup>
import Child from './Child.vue';
import { ref } from 'vue';
const childComponent = ref(null);
</script>

2. 调用子组件暴露的方法。通过父组件的方法触发对子组件方法的调用,并展示调用后的效果。可以在父组件的方法中,使用childComponent.value来访问子组件暴露的方法,并执行相应的操作。例如:

<template>
  <div>
    <button @click="callChildMethod">触发子组件方法</button>
    <Child ref="childComponent" />
  </div>
</template>
<script setup>
import Child from './Child.vue';
import { ref } from 'vue';
const childComponent = ref(null);
const callChildMethod = () => {
  if (childComponent.value) {
    childComponent.value.childMethod();
  }
};
</script>

当点击父组件的按钮时,会触发callChildMethod方法,该方法会调用子组件暴露的childMethod方法,并展示调用后的效果。通过这种方式,父组件可以方便地与子组件进行交互,实现特定的功能。

四、defineExpose 的原理

image.png

  1. 编译后的代码分析。编译后的子组件代码结构中,_sfc_main对象的setup函数接收__props和一个包含expose方法的对象。在子组件使用setup后,原本的defineExpose宏函数在编译后变成了__expose方法。这个方法是在createSetupContext函数中定义的,具体来说,createSetupContext函数接收一个instance参数,这个参数就是当前的 Vue 实例对象。在函数内部,定义了一个expose函数,这个函数将子组件需要暴露的属性或方法组成的对象赋值给vue实例上的exposed属性。

  2. __expose方法的作用。在createSetupContext函数中,__expose方法通过expose函数将子组件需要暴露的属性或方法组成的对象赋值给vue实例上的exposed属性。具体来说,instance.exposed = exposed || {};这行代码将子组件需要暴露的内容存储在vue实例的exposed属性上,以便后续父组件可以访问这些内容。

  3. 父组件访问子组件方法的过程。在 Vue3 中,父组件想要访问子组件的方法,需要使用特殊的ref attribute。在示例中,父组件通过给子组件绑定了ref属性,此时child变量的值就是一个名为getExposeProxy函数的返回值。getExposeProxy函数的作用是返回instance.exposed的Proxy对象。当父组件使用child.value.validate访问子组件的validate方法时,实际上是访问的instance.exposed对象中的validate方法,而这个方法就是通过defineExpose宏函数暴露的方法。 image.png

五、总结

总之,defineExpose在 Vue3 的组件通信中起着至关重要的作用,它为开发者提供了一种更加灵活、可控的组件间通信方式,有助于提高代码的可读性、可维护性和可扩展性。