彻底搞清楚vue3的defineExpose宏函数是如何暴露方法给父组件使用

vue3,defineexpose · 浏览次数 : 0

小编点评

**defineExpose宏** `defineExpose`宏函数允许我们在子组件中指定想要暴露出去的属性和方法。在父组件中,我们可以使用`import.meta.defineExpose`访问这些属性和方法。 **示例** ```vue // child.vue ``` ```vue // index.vue ``` **使用 defineExpose 宏的优点** * 允许我们暴露子组件中的特定属性和方法。 * 父组件可以访问子组件中的属性和方法,即使子组件使用的是 setup 模式。 * 可以控制暴露的属性和方法的名称。 **注意** * `defineExpose` 只能用于在 render 中使用的属性和方法。 * 父组件可以使用 `import.meta.defineExpose` 访问定义的属性和方法。 * 当子组件使用 `defineExpose` 时,父组件需要提供 `ref` 或者其他方法来访问子组件。

正文

前言

众所周知,当子组件使用setup后,父组件就不能像vue2那样直接就可以访问子组件内的属性和方法。这个时候就需要在子组件内使用defineExpose宏函数来指定想要暴露出去的属性和方法。这篇文章来讲讲defineExpose宏函数是如何暴露出去这些属性和方法给父组件使用。注:本文中使用的vue版本为3.4.19

看个demo

父组件index.vue的代码如下:

<template>
  <ChildDemo ref="child" />
  <button @click="handleClick">调用子组件的validate方法</button>
</template>

<script setup lang="ts">
import ChildDemo from "./child.vue";
import { ref } from "vue";

const child = ref();

function handleClick() {
  console.log(child.value.validate);
  child.value.validate?.();
}
</script>

上面的代码很简单,通过ref拿到子组件的实例赋值给child变量。然后在按钮的click事件中打印出子组件的validate方法和执行validate方法。

再来看看子组件child.vue不使用defineExpose宏的例子,代码如下:

<template></template>

<script setup>
function validate() {
  console.log("执行子组件validate方法");
}
</script>

在浏览器中点击父组件的button按钮,可以看到控制台中打印的是undefined,并且子组件内的validate方法也没有执行。因为子组件使用了setup,默认是不会暴露setup中定义的属性和方法。如下图:
no-defineExpose

我们再来看看子组件child.vue使用defineExpose宏的例子,代码如下:

<template></template>

<script setup>
function validate() {
  console.log("执行子组件validate方法");
}

defineExpose({
  validate,
});
</script>

在浏览器中点击父组件的button按钮,可以看到控制台中打印的不再是undefined,子组件内的validate方法也执行了。如下图:
has-defineExpose

关注公众号:【前端欧阳】,给自己一个进阶vue的机会

加我微信heavenyjj0012回复「666」,免费领取欧阳研究vue源码过程中收集的源码资料,欧阳写文章有时也会参考这些资料。同时让你的朋友圈多一位对vue有深入理解的人。

编译后的代码

首先需要在浏览器中找到编译后的使用defineExpose宏的child.vue文件,在network面板中找到child.vue,然后右键点击Open in Sources panel就可以在source面板中找到编译后的child.vue。如下图:
network

为了要在浏览器中debug,我们还需要在设置中关闭浏览器的javascript source map,如下图:
source-map

现在我们来看看编译后的child.vue文件,代码如下:

const _sfc_main = {
  __name: "child",
  setup(__props, { expose: __expose }) {
    function validate() {
      console.log("执行子组件validate方法");
    }
    __expose({
      validate,
    });
    const __returned__ = { validate };
    return __returned__;
  },
};

function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  return null;
}
_sfc_main.render = _sfc_render;
export default _sfc_main;

从上面可以看到_sfc_main对象中的setup对应的就是我们源代码<script setup>中的内容,并且defineExpose宏函数也不在了,变成了一个__expose方法(defineExpose宏函数如何编译成__expose方法我们会在下一篇文章讲)。如下图:
convert

expose方法

__expose方法打个断点,刷新页面此时断点停留在__expose方法上面。点击step into进入到__expose方法内部,如下图:
step-into

进入到__expose方法内部,我们发现__expose方法是在一个createSetupContext函数中定义的。在我们这个场景中createSetupContext函数简化后的代码如下:

function createSetupContext(instance) {
  const expose = (exposed) => {
    instance.exposed = exposed || {};
  };

  return Object.freeze({
    // ...省略
    expose,
  });
}

我们先来看看函数中的instance变量,我想你通过名字应该已经猜到了他就是当前vue实例对象。如下图:
instance

在vue实例对象中有我们熟悉的data方法、directives和componens属性等。

expose函数内部做的事情也很简单,将子组件需要暴露的属性或者方法组成的对象赋值给vue实例上的exposed属性。

父组件访问子组件的validate方法

在vue3中想要访问子组件需要使用特殊的 ref attribute,在我们这个例子中就是使用<ChildDemo ref="child" />。这样使用后就可以使用child变量访问子组件,其实在这里child变量的值就是一个名为getExposeProxy函数的返回值(后面的文章中会去详细讲解ref attribute是如何访问子组件)。

getExposeProxy函数的代码如下:

function getExposeProxy(instance) {
  if (instance.exposed) {
    return (
      instance.exposeProxy ||
      (instance.exposeProxy = new Proxy(proxyRefs(markRaw(instance.exposed)), {
        get(target, key) {
          if (key in target) {
            return target[key];
          } else if (key in publicPropertiesMap) {
            return publicPropertiesMap[key](instance);
          }
        },
        has(target, key) {
          // ...省略
        },
      }))
    );
  }
}

前面我们讲过了defineExpose宏函数中定义了想要暴露出来的属性和方法,经过编译后defineExpose宏函数变成了__expose方法。执行__expose方法后会将子组件想要暴露的属性或者方法组成的对象赋值给vue实例上的exposed属性,也就是instance.exposed

在上面的getExposeProxy函数中就是返回了instance.exposedProxy对象,当我们使用child.value.validate访问子组件的validate方法,其实就是访问的是instance.exposed对象中的validate方法,而instance.exposed中的validate方法就是defineExpose宏函数暴露的validate方法。如下图:
full-progress

总结

父组件想要访问子组件暴露的validate方法主要分为下面四步:

  • 子组件使用defineExpose宏函数声明想要暴露validate方法。

  • defineExpose宏函数经过编译后变成__expose方法。

  • 执行__expose方法将子组件需要暴露的属性或者方法组成的对象赋值给子组件vue实例上的exposed属性,也就是instance.exposed

  • 父组件使用ref访问子组件的validate方法,也就是访问child.value.validate。其实访问的就是上一步的instance.exposed.validate方法,最终访问的就是defineExpose宏函数中暴露的validate方法。

关注(图1)公众号:【前端欧阳】,解锁我更多vue原理文章。
加我(图2)微信回复「666」,免费领取欧阳研究vue源码过程中收集的源码资料,欧阳写文章有时也会参考这些资料。同时让你的朋友圈多一位对vue有深入理解的人。
公众号微信

与彻底搞清楚vue3的defineExpose宏函数是如何暴露方法给父组件使用相似的内容:

彻底搞清楚vue3的defineExpose宏函数是如何暴露方法给父组件使用

前言 众所周知,当子组件使用setup后,父组件就不能像vue2那样直接就可以访问子组件内的属性和方法。这个时候就需要在子组件内使用defineExpose宏函数来指定想要暴露出去的属性和方法。这篇文章来讲讲defineExpose宏函数是如何暴露出去这些属性和方法给父组件使用。注:本文中使用的vu

彻底搞清楚vue3的defineExpose宏函数是如何暴露方法给父组件使用

众所周知,当子组件使用setup后,父组件就不能像vue2那样直接就可以访问子组件内的属性和方法。这个时候就需要在子组件内使用defineExpose宏函数来指定想要暴露出去的属性和方法

彻底搞懂JavaScript原型和原型链

基于原型编程 在面向对象的编程语言中,类和对象的关系是铸模和铸件的关系,对象总是从类创建而来,比如Java中,必须先创建类再基于类实例化对象。 而在基于原型编程的思想中,类并不是必须的,对象都是通过克隆另外一个对象而来,这个被克隆的对象就是原型对象。 基于原型编程的语言通常遵循下面的规则: 所有的数

带你彻底搞懂递归时间复杂度的Master公式

网上找到的Master公式推导过程都太过于复杂了,为此我特地找到一种小白也能看懂的推导过程。看完这篇文章后,你会对递归的时间复杂度深谙于心,打死都不会忘记。

[转帖]面渣逆袭:二十二图、八千字、二十问,彻底搞定MyBatis!

https://cdn.modb.pro/db/334793 大家好,我是老三,面渣逆袭系列继续,这节我们的主角是MyBatis,作为当前国内最流行的ORM框架,是我们这些crud选手最趁手的工具,赶紧来看看面试都会问哪些问题吧。 基础 1.说说什么是MyBatis? MyBatis logo 先吹

彻底理解闭包实现原理

前言 闭包对于一个长期写 Java 的开发者来说估计鲜有耳闻,我在写 Python 和 Go 之前也是没怎么了解,光这名字感觉就有点"神秘莫测",这篇文章的主要目的就是从编译器的角度来分析闭包,彻底搞懂闭包的实现原理。 函数一等公民 一门语言在实现闭包之前首先要具有的特性就是:First class

手动实现Transformer

Transformer和BERT可谓是LLM的基础模型,彻底搞懂极其必要。Transformer最初设想是作为文本翻译模型使用的,而BERT模型构建使用了Transformer的部分组件,如果理解了Transformer,则能很轻松地理解BERT。 一.Transformer模型架构 1.编码器 (

彻底理解Linux的DISPLAY变量的作用

背景 最近遇到个两年前遇到的问题,使用virt-manager提示(virt-manager:873): Gtk-WARNING **: 14:53:28.147: cannot open display: :1,当时专门运维的同事帮忙临时调了下DISPLAY变量,好像是将:1改成了SSH用户本地I

[转帖]彻底理解并解决服务器出现大量TIME_WAIT

https://zhuanlan.zhihu.com/p/567088021?utm_id=0 首先我们说下状态 TIME_WAIT 出现的原因 TCP的新建连接,断开连接的流程和各个状态,如下图所示 由上图可知:TIME_WAIT 是主动断开连接的一方会出现的,客户端,服务器都有可能出现 当客户端

彻底弄懂ip掩码中的网络地址、广播地址、主机地址

本文为博主原创,转载请注明出处: 概念理解: IP掩码(或子网掩码)用于确定一个IP地址的网络部分和主机部分。它是一个32位的二进制数字,与IP地址做逻辑与运算,将IP地址划分为网络地址和主机地址两部分。 在理解IP地址段中的网络地址、广播地址和主机地址之前,首先需要了解IP地址的构成。IP地址由网