Vue3中DefineEmits、DefineProps 是怎么做到不用引入就能直接使用
最近正在將一個使用單文件組件的 Options API 的 Vue2 JavaScript 項目升級為 Vue3 typescript,并利用 Composition API 的優(yōu)勢。
比如,下面這種 選項API 方式:
export default {
props: {
name: {
type: String,
required: true.
}
},
emits: ['someEvent', 'increaseBy']
};
我們將它轉(zhuǎn)成 組合API 方式:
const props = defineProps<{
name: string;
}>();
const emit = defineEmits<{
(event: 'someEvent): void;
(event: 'increaseBy', value: number): void;
}>();
從 選項API 的 emit 和 props 到 組合API 的 defineemit 和 defineProps 函數(shù)的基于類型語法的轉(zhuǎn)換并不簡單。我也很好奇 Vue 是如何處理接口的。
TypeScript 接口是只在設(shè)計和編譯時存在的結(jié)構(gòu)。它們在JavaScript運(yùn)行時之前被過濾掉,那么它們是如何影響組件的行為的呢?
我想知道是否有辦法看到Vue如何解釋傳遞給 defineEmits 和 defineProps 的通用參數(shù)。如果你注意到文檔中說你不需要導(dǎo)入 defineEmits 和 defineProps 函數(shù)。這是因為它們實(shí)際上是同名的JavaScript函數(shù)的宏。在進(jìn)行完整的 TypeScript 傳遞之前,Vue webpack插件使用TypeScript的 AST(抽象語法樹)來推導(dǎo)JavaScript版本的函數(shù)選項。
如果不是因為宏:
defineProps<{
prop1: string;
prop2: number;
}>();
就會變成:
defineProps();
這樣就會導(dǎo)致參數(shù)缺失的錯誤。
如果看一下Vue的 SFC(單文件組件)編譯器源代碼,有一個叫做 compileScript 的函數(shù)。我開始嘗試用最少的參數(shù)來調(diào)用這個函數(shù),這樣就不會出錯,并模擬任何不重要的必要參數(shù)。最終發(fā)現(xiàn)了另一個叫 parse 的函數(shù)。這給了我所需的大部分參數(shù),只剩下要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 對象。原始類型是一對一的。接口變成對象,而 ? 可選語法驅(qū)動 required 的屬性。