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

前端欧阳 2024-07-09 15:33:03 阅读 89

前言

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

打包领取欧阳平时写文章都会参考的vue源码资料、解锁我更多vue原理文章

看个demo

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

<template>

<ChildDemo ref="child" />code>

<button @click="handleClick">调用子组件的validate方法</button>code>

</template>

<script setup lang="ts">code>

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

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

<template></template>

<script setup>

function validate() {

console.log("执行子组件validate方法");

}

defineExpose({

validate,

});

</script>

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

has-defineExpose

编译后的代码

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

network

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

source-map

现在我们来看看编译后的<code>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

<code>expose方法

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

step-into

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

function createSetupContext(instance) {

const expose = (exposed) => {

instance.exposed = exposed || {};

};

return Object.freeze({

// ...省略

expose,

});

}

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

instance

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

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

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

在vue3中想要访问子组件需要使用特殊的 ref attribute,在我们这个例子中就是使用<ChildDemo ref="child" />code>。这样使用后就可以使用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

总结

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

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

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

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

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

打包领取欧阳平时写文章都会参考的vue源码资料、解锁我更多vue原理文章



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。