vue3的宏到底是什麼東西?

前言

vue3開始vue引入了宏,比如definePropsdefineEmits等。我們每天寫vue代碼時都會使用到這些宏,但是你有沒有思考過vue中的宏到底是什麼?爲什麼這些宏不需要手動從vueimport?爲什麼只能在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,屬性名爲contentprops,並且在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 = renderrender函數賦值到組件對象的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的宏是一種特殊的代碼,在編譯時會將這些特殊的代碼轉換爲瀏覽器能夠直接運行的指定代碼,根據宏的功能不同,轉換後的代碼也不同。

  • 爲什麼這些宏不需要手動從vueimport

    因爲在編譯時已經將這些宏替換爲指定的瀏覽器能夠直接運行的代碼,在運行時已經不存在這些宏相關的代碼,自然不需要從vueimport

  • 爲什麼只能在setup頂層中使用這些宏?

    因爲在編譯時只會去處理setup頂層的宏,其他地方的宏會原封不動的輸出回來。在運行時由於我們沒有在任何地方定義這些宏,當代碼執行到宏的時候當然就會報錯。

如果想要在vue中使用更多的宏,可以使用 vue macros。這個庫是用於在vue中探索更多的宏和語法糖,作者是vue的團隊成員 三咲智子

如果我的文章對你有點幫助,歡迎關注公衆號:【歐陽碼農】,文章在公衆號首發。你的支持就是我創作的最大動力,感謝感謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章