前言 從vue3開始vue引入了巨集,比如defineProps、defineEmits等。我們每天寫vue代碼時都會使用到這些巨集,但是你有沒有思考過vue中的巨集到底是什麼?為什麼這些巨集不需要手動從vue中import?為什麼只能在setup頂層中使用這些巨集? vue 文件如何渲染到瀏覽器上 要回答上 ...
前言
從vue3
開始vue
引入了巨集,比如defineProps
、defineEmits
等。我們每天寫vue
代碼時都會使用到這些巨集,但是你有沒有思考過vue
中的巨集到底是什麼?為什麼這些巨集不需要手動從vue
中import
?為什麼只能在setup
頂層中使用這些巨集?
vue 文件如何渲染到瀏覽器上
要回答上面的問題,我們先來瞭解一下從一個vue
文件到渲染到瀏覽器這一過程經歷了什麼?
我們的vue
代碼一般都是寫在尾碼名為vue的文件上,顯然瀏覽器是不認識vue文件的,瀏覽器只認識html、css、jss等文件。所以第一步就是通過webpack
或者vite
將一個vue文件編譯為一個包含render
函數的js
文件。然後執行render
函數生成虛擬DOM,再調用瀏覽器的DOM API
根據虛擬DOM生成真實DOM掛載到瀏覽器上。
vue3的巨集是什麼?
我們先來看看vue
官方的解釋:
巨集是一種特殊的代碼,由編譯器處理並轉換為其他東西。它們實際上是一種更巧妙的字元串替換形式。
巨集是在哪個階段運行?
通過前面我們知道了vue
文件渲染到瀏覽器上主要經歷了兩個階段。
第一階段是編譯時,也就是從一個vue
文件經過webpack
或者vite
編譯變成包含render函數的js文件。此時的運行環境是nodejs
環境,所以這個階段可以調用nodejs
相關的api
,但是沒有在瀏覽器環境內執行,所以不能調用瀏覽器的API
。
第二階段是運行時,此時瀏覽器會執行js
文件中的render
函數,然後依次生成虛擬DOM
和真實DOM
。此時的運行環境是瀏覽器環境內,所以可以調用瀏覽器的API,但是在這一階段中是不能調用nodejs
相關的api
。
而巨集就是作用於編譯時,也就是從vue文件編譯為js文件這一過程。
舉個defineProps
的例子:在編譯時defineProps
巨集就會被轉換為定義props
相關的代碼,當在瀏覽器運行時自然也就沒有了defineProps
巨集相關的代碼了。所以才說巨集是在編譯時執行的代碼,而不是運行時執行的代碼。
一個defineProps
巨集的例子
我們來看一個實際的例子,下麵這個是我們的源代碼:
<template>
<div>content is {{ content }}</div>
<div>title is {{ title }}</div>
</template>
<script setup lang="ts">
import {ref} from "vue"
const props = defineProps({
content: String,
});
const title = ref("title")
</script>
在這個例子中我們使用defineProps
巨集定義了一個類型為String
,屬性名為content
的props
,並且在template
中渲染content
的內容。
我們接下來再看看編譯成js
文件後的代碼,代碼我已經進行過簡化:
import { defineComponent as _defineComponent } from "vue";
import { ref } from "vue";
const __sfc__ = _defineComponent({
props: {
content: String,
},
setup(__props) {
const props = __props;
const title = ref("title");
const __returned__ = { props, title };
return __returned__;
},
});
import {
toDisplayString as _toDisplayString,
createElementVNode as _createElementVNode,
Fragment as _Fragment,
openBlock as _openBlock,
createElementBlock as _createElementBlock,
} from "vue";
function render(_ctx, _cache, $props, $setup) {
return (
_openBlock(),
_createElementBlock(
_Fragment,
null,
[
_createElementVNode(
"div",
null,
"content is " + _toDisplayString($props.content),
1 /* TEXT */
),
_createElementVNode(
"div",
null,
"title is " + _toDisplayString($setup.title),
1 /* TEXT */
),
],
64 /* STABLE_FRAGMENT */
)
);
}
__sfc__.render = render;
export default __sfc__;
我們可以看到編譯後的js
文件主要由兩部分組成,第一部分為執行defineComponent
函數生成一個 __sfc__
對象,第二部分為一個render
函數。render
函數不是我們這篇文章要講的,我們主要來看看這個__sfc__
對象。
看到defineComponent
是不是覺得很眼熟,沒錯這個就是vue
提供的API中的 definecomponent函數。這個函數在運行時沒有任何操作,僅用於提供類型推導。這個函數接收的第一個參數就是組件選項對象,返回值就是該組件本身。所以這個__sfc__
對象就是我們的vue
文件中的script
代碼經過編譯後生成的對象,後面再通過__sfc__.render = render
將render
函數賦值到組件對象的render
方法上面。
我們這裡的組件選項對象經過編譯後只有兩個了,分別是props
屬性和setup
方法。明顯可以發現我們原本在setup
裡面使用的defineProps
巨集相關的代碼不在了,並且多了一個props
屬性。沒錯這個props
屬性就是我們的defineProps
巨集生成的。
我們再來看一個不在setup
頂層調用defineProps
的例子:
<script setup lang="ts">
import {ref} from "vue"
const title = ref("title")
if (title.value) {
const props = defineProps({
content: String,
});
}
</script>
運行這個例子會報錯:defineProps is not defined
我們來看看編譯後的js代碼:
import { defineComponent as _defineComponent } from "vue";
import { ref } from "vue";
const __sfc__ = _defineComponent({
setup(__props) {
const title = ref("title");
if (title.value) {
const props = defineProps({
content: String,
});
}
const __returned__ = { title };
return __returned__;
},
});
明顯可以看到由於我們沒有在setup
的頂層調用defineProps
巨集,在編譯時就不會將defineProps
巨集替換為定義props
相關的代碼,而是原封不動的輸出回來。在運行時執行到這行代碼後,由於我們沒有任何地方定義了defineProps
函數,所以就會報錯defineProps is not defined
。
總結
現在我們能夠回答前面提的三個問題了。
-
vue
中的巨集到底是什麼?vue3
的巨集是一種特殊的代碼,在編譯時會將這些特殊的代碼轉換為瀏覽器能夠直接運行的指定代碼,根據巨集的功能不同,轉換後的代碼也不同。 -
為什麼這些巨集不需要手動從
vue
中import
?因為在編譯時已經將這些巨集替換為指定的瀏覽器能夠直接運行的代碼,在運行時已經不存在這些巨集相關的代碼,自然不需要從
vue
中import
。 -
為什麼只能在
setup
頂層中使用這些巨集?因為在編譯時只會去處理
setup
頂層的巨集,其他地方的巨集會原封不動的輸出回來。在運行時由於我們沒有在任何地方定義這些巨集,當代碼執行到巨集的時候當然就會報錯。
如果想要在vue
中使用更多的巨集,可以使用 vue macros。這個庫是用於在vue中探索更多的巨集和語法糖,作者是vue的團隊成員 三咲智子 。
如果我的文章對你有點幫助,歡迎關註公眾號:【歐陽碼農】,文章在公眾號首發。你的支持就是我創作的最大動力,感謝感謝!