前言
眾所周知,當子元件使用setup後,父元件就不能像vue2那樣直接就可以訪問子元件內的屬性和方法。這個時候就需要在子元件內使用defineExpose
宏函式來指定想要暴露出去的屬性和方法。這篇文章來講講defineExpose
宏函式是如何暴露出去這些屬性和方法給父元件使用。注:本文中使用的vue版本為3.4.19
。
關注公眾號:【前端歐陽】,給自己一個進階vue的機會
加我微信heavenyjj0012回覆「666」,免費領取歐陽研究vue原始碼過程中收集的原始碼資料,歐陽寫文章有時也會參考這些資料。同時讓你的朋友圈多一位對vue有深入理解的人。
看個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中定義的屬性和方法。如下圖:
我們再來看看子元件child.vue
使用defineExpose
宏的例子,程式碼如下:
<template></template>
<script setup>
function validate() {
console.log("執行子元件validate方法");
}
defineExpose({
validate,
});
</script>
在瀏覽器中點選父元件的button按鈕,可以看到控制檯中列印的不再是undefined
,子元件內的validate
方法也執行了。如下圖:
關注公眾號:【前端歐陽】,給自己一個進階vue的機會
加我微信heavenyjj0012回覆「666」,免費領取歐陽研究vue原始碼過程中收集的原始碼資料,歐陽寫文章有時也會參考這些資料。同時讓你的朋友圈多一位對vue有深入理解的人。
編譯後的程式碼
首先需要在瀏覽器中找到編譯後的使用defineExpose
宏的child.vue
檔案,在network皮膚中找到child.vue
,然後右鍵點選Open in Sources panel就可以在source皮膚中找到編譯後的child.vue
。如下圖:
為了要在瀏覽器中debug,我們還需要在設定中關閉瀏覽器的javascript 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
方法我們會在下一篇文章講)。如下圖:
expose
方法
給__expose
方法打個斷點,重新整理頁面此時斷點停留在__expose
方法上面。點選step into
進入到__expose
方法內部,如下圖:
進入到__expose
方法內部,我們發現__expose
方法是在一個createSetupContext
函式中定義的。在我們這個場景中createSetupContext
函式簡化後的程式碼如下:
function createSetupContext(instance) {
const expose = (exposed) => {
instance.exposed = exposed || {};
};
return Object.freeze({
// ...省略
expose,
});
}
我們先來看看函式中的instance
變數,我想你透過名字應該已經猜到了他就是當前vue例項物件。如下圖:
在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.exposed
的Proxy
物件,當我們使用child.value.validate
訪問子元件的validate
方法,其實就是訪問的是instance.exposed
物件中的validate
方法,而instance.exposed
中的validate
方法就是defineExpose
宏函式暴露的validate
方法。如下圖:
總結
父元件想要訪問子元件暴露的validate
方法主要分為下面四步:
-
子元件使用
defineExpose
宏函式宣告想要暴露validate
方法。 -
defineExpose
宏函式經過編譯後變成__expose
方法。 -
執行
__expose
方法將子元件需要暴露的屬性或者方法組成的物件賦值給子元件vue例項上的exposed
屬性,也就是instance.exposed
。 -
父元件使用ref訪問子元件的
validate
方法,也就是訪問child.value.validate
。其實訪問的就是上一步的instance.exposed.validate
方法,最終訪問的就是defineExpose
宏函式中暴露的validate
方法。
關注公眾號:【前端歐陽】,給自己一個進階vue的機會
加我微信heavenyjj0012回覆「666」,免費領取歐陽研究vue原始碼過程中收集的原始碼資料,歐陽寫文章有時也會參考這些資料。同時讓你的朋友圈多一位對vue有深入理解的人。