Vue3中defineEmits、defineProps 是怎麼做到不用引入就能直接用的

前端小智發表於2023-04-11
本文首發於微信公眾號:大遷世界, 我的微信:qq449245884,我會第一時間和你分享前端行業趨勢,學習途徑等等。
更多開源作品請看 GitHub https://github.com/qq449245884/xiaozhi ,包含一線大廠面試完整考點、資料以及我的系列文章。

最近正在將一個使用單檔案元件的 Options API 的 Vue2 JavaScript 專案升級為 Vue3 typescript,並利用 Composition API 的優勢。

比如,下面這種 選項API 方式:

export default {
  props: {
    name: {
      type: String,
      required: true.
    }
  },
  emits: ['someEvent', 'increaseBy']
};

我們將它轉成 組合API 方式:

 const props = defineProps<{
  name: string;
 }>();
 const emit = defineEmits<{
  (event: 'someEvent): void;
  (event: 'increaseBy', value: number): void;
 }>();

選項APIemitprops組合APIdefineemitdefineProps 函式的基於型別語法的轉換並不簡單。我也很好奇 Vue 是如何處理介面的。

TypeScript 介面是隻在設計和編譯時存在的結構。它們在JavaScript執行時之前被過濾掉,那麼它們是如何影響元件的行為的呢?

我想知道是否有辦法看到Vue如何解釋傳遞給 defineEmitsdefineProps 的通用引數。如果你注意到文件中說你不需要匯入 defineEmitsdefineProps 函式。這是因為它們實際上是同名的JavaScript函式的。在進行完整的 TypeScript 傳遞之前,Vue webpack外掛使用TypeScript的 AST(抽象語法樹)來推導JavaScript版本的函式選項。

如果不是因為宏:

 defineProps<{
    prop1: string;
    prop2: number;
  }>();

就會變成:

 defineProps();

這樣就會導致引數缺失的錯誤。

如果看一下Vue的 SFC(單檔案元件)編譯器原始碼,有一個叫做 compileScript 的函式。我開始嘗試用最少的引數來呼叫這個函式,這樣就不會出錯,並模擬任何不重要的必要引數。最終發現了另一個叫 parse 的函式。這給了我所需的大部分引數,只剩下要mock的元件 id

這裡有一個小指令碼,它接收SFC的 .vue檔案並輸出 Vue 如何解釋 TypeScript。

import { readFile, writeFile } from "fs";
import parseArgs from "minimist";
import { parse, compileScript } from "@vue/compiler-sfc";
const { file, out } = parseArgs(process.argv.slice(2), {
  string: ["file", "out"],
  alias: {
    file: "f",
    out: "o"
  }
});
const filename = file;
const mockId = "xxxxxxxx";
readFile(filename, "utf8", (err, data) => {
  const { descriptor } = parse(data, {
    filename
  });
  const { content } = compileScript(descriptor, {
    inlineTemplate: true,
    templateOptions: {
      filename
    },
    id: mockId
  });
  if (out) {
    writeFile(out, "utf8", content);
  } else {
    process.stdout.write(content);
  }
});
事例地址:https://stackblitz.com/edit/node-fzuykn?file=index.js

例如,有如以下元件:

interface Bar {
  prop1: string;
  prop2: number;
}

defineProps<{
  bar: Bar;
  bars: Bar[];
  asdf1?: boolean;
  asdf2: string[];
}>();

輸出:

interface Bar {
  prop1: string;
  prop2: number;
}

export default /*#__PURE__*/_defineComponent({
  __name: 'demo',
  props: {
    bar: { type: Object, required: true },
    bars: { type: Array, required: true },
    asdf1: { type: Boolean, required: false },
    asdf2: { type: Array, required: true }
  },
  setup(__props: any) {
    return (_ctx: any,_cache: any) => {
      return (_openBlock(), _createElementBlock("div"))
    }
}

正如上面所看到的,SFC編譯器採用TypeScript型別資訊,並建立了 props 物件。原始型別是一對一的。介面變成物件,而 ? 可選語法驅動 required 的屬性。

程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug

作者:romaopedro 譯者:前端小智 來源:logrocket
https://unicorn-utterances.com/posts/vue-composition-inspector

交流

有夢想,有乾貨,微信搜尋 【大遷世界】 關注這個在凌晨還在刷碗的刷碗智。

本文 GitHub https://github.com/qq449245884/xiaozhi 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。

相關文章