最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

為了 Vue 組件測試,你需要為每個(gè)事件綁定的方法加上括號(hào)嗎?

2023-11-17 11:17 作者:OpenTiny社區(qū)  | 我要投稿

本文由華為云體驗(yàn)技術(shù)團(tuán)隊(duì)松塔同學(xué)分享

先說結(jié)論,當(dāng)然不是!Vue 組件測試,尤其是組件觸發(fā)事件的測試,有成熟的示例。我們同樣要關(guān)注測試的原則,例如將組件當(dāng)成黑盒,不關(guān)心其內(nèi)部實(shí)現(xiàn),而只關(guān)心與其交互。本文是借由一次 Vue 組件測試,引發(fā)對(duì) Vue 源碼和 Spy 函數(shù)的延伸探討。

假設(shè)你寫了一個(gè) Vue 組件,它大概長這樣:

它定義了datadisabled作為 props,前者作為組件的數(shù)據(jù)輸入,后者用來定義組件的功能開關(guān)。組件被點(diǎn)擊時(shí),會(huì)拋出confirm事件,不過當(dāng)disabledtrue時(shí),confirm事件不會(huì)被觸發(fā)。

當(dāng)你想為這個(gè)組件寫一些單元測試時(shí),可能會(huì)這樣寫:

valid初始化時(shí)為false,即MyComponent一開始不會(huì)拋出confirm事件,當(dāng)valid被改變后,點(diǎn)擊MyComponent,confirm事件才被拋出。

這段單元測試會(huì)在最后一句報(bào)錯(cuò),顯示spy實(shí)際被觸發(fā) 0 次。實(shí)際上,spy永遠(yuǎn)不會(huì)被觸發(fā),即使valid初始化時(shí)為true也是如此。

然而,將模板里的方法調(diào)用調(diào)整一下,加上括號(hào),單元測試就按照預(yù)期通過了:

為什么加不加括號(hào)會(huì)引起單元測試的邏輯變化?

模板語法

首先我們需要看一看模板在編譯時(shí),處理@confirm="handleConfirm()"@confirm="handleConfirm"有什么不同。

@vue/compiler-sfccompileTemplate方法開始一路往下分析,會(huì)發(fā)現(xiàn)模板編譯的核心方法是@vue/compiler-core這個(gè)包中的baseCompile方法。這個(gè)方法主要干三件事:


  1. 調(diào)用baseParse方法解析 HTML,生成基礎(chǔ)的 AST。由于 Vue 在 HTML 上增加了許多語法特性(v-if、v-for、v-bind 等等),需要做對(duì)應(yīng)解析。

<div @click="handleConfirm()" />?生成的 AST

<div @click="handleConfirm" />?生成的 AST


查看生成的 AST 結(jié)構(gòu)后可以發(fā)現(xiàn),加不加括號(hào)對(duì)結(jié)構(gòu)并不會(huì)產(chǎn)生影響。二者都生成了 v-on 的 prop,exp中的 content 未對(duì)原始內(nèi)容做出改動(dòng)。

  1. 進(jìn)一步對(duì) AST 做解析和轉(zhuǎn)換。這一步引入了nodeTransformsdirectiveTransforms對(duì)象,其實(shí)是在./transforms目錄下的一系列函數(shù):

光從名字就可以看出來,依舊是對(duì) Vue 的語法特性做的一些工作,最終在 AST 的每個(gè)節(jié)點(diǎn)上增加codegenNode,這個(gè)屬性將會(huì)被用在第三步生成渲染函數(shù)過程中。經(jīng)過 transform 這一步后,生成的codegenNode如下:

<div @click="handleConfirm()" />?的?codegenNode

<div @click="handleConfirm" />?的?codegenNode


二者 prop 中的 value 值有所差異,type 是 typescript 定義的 enum,編譯后變成了數(shù)字,還原后前者的類型從SIMPLE_EXPRESSION變成了COMPOUND_EXPRESSION,后者仍保持之前的SIMPLE_EXPRESSION。

造成二者差異的原因,需要深入transformOn這個(gè)對(duì) v-on 語法轉(zhuǎn)換的方法。它根據(jù) AST 節(jié)點(diǎn)的exparg,生成codegenNodeprops下的屬性。簡化一下有關(guān)exp的邏輯,核心代碼如下:

首先對(duì)exp做判斷,是否是 member expression、是否是 inline statement,是否有多個(gè) statement。然后出現(xiàn)了exp的改寫,根據(jù)判斷生成了 compound expression,實(shí)際就是轉(zhuǎn)換成了函數(shù)表達(dá)??磥?code>isMemberExp、isInlineStatement這兩個(gè)判斷影響了最終codegenNode的生成。

Member Expression

這是個(gè)來源于 AST 定義的概念,JavaScript 中經(jīng)常有對(duì)象屬性的指向,例如:

這里a.x就是 member expression,transformOn中調(diào)用isMemberExpression來做判斷,實(shí)際就是調(diào)用 babel parser 的能力分析,簡化來說:

這里 MemberExpression、OptionalMemberExpression、Identifier 都被認(rèn)定成了 member expression。OptionalMemberExpression 即帶有 optional chaining (?.) 的表達(dá)式。Identifier 也被包括的原因是,在模板中一般會(huì)省略主對(duì)象,如 this、或者 setup 中返回的對(duì)象。

<div @click="handleConfirm" />handleConfirm就是 Identifier,它指向的就是我們?cè)?script 中定義的函數(shù)。

isInlineStatement的判斷中還出現(xiàn)了一個(gè)條件fnExpRE.test(exp.content),這是函數(shù)表達(dá)式的正則判斷:

雖然直接在模板里聲明函數(shù)很罕見,但是 Vue 并沒有限制這種做法。

exp如果既不是 member expression,也不是函數(shù)表達(dá)式,transformOn就把它當(dāng)作 inline statement。實(shí)際上這是我們?cè)谌粘J褂脮r(shí)比較常見的作法,例如只是簡單對(duì)變量賦值,那就無需在<script>中聲明函數(shù),而是簡寫為:


而讓這段代碼生效的原因,就在于transformOn編譯時(shí)將exp包裹了一層函數(shù)聲明。它調(diào)用createCompoundExpression,將$event 作為函數(shù)入?yún)ⅲ瑥亩购瘮?shù)內(nèi)能獲取到:

3. 由上一步生成的codegenNode,轉(zhuǎn)換成最終的渲染函數(shù)。重點(diǎn)看一下帶括號(hào)的表達(dá)式生成的渲染函數(shù):

with statement 是在模板中可以省略 this 的原因。

對(duì)比

將以上分析做一個(gè)總結(jié),我們可以將編譯后結(jié)果簡化一下,那么帶括號(hào)的函數(shù)表達(dá):

不帶括號(hào)的函數(shù)表達(dá):

Mock Function

我們已經(jīng)搞清楚在編譯階段,帶不帶括號(hào)的函數(shù)表達(dá)有什么區(qū)別。接下來就要研究這個(gè)區(qū)別對(duì)于 Mock 行為產(chǎn)生了什么影響。Vitest 內(nèi)部利用 tinyspy 來實(shí)現(xiàn) mock 功能,本文并不會(huì)深入 tinyspy 的具體實(shí)現(xiàn),因?yàn)?JavaScript spy 庫大同小異,而背后的 JavaScript 語言特性才是本文真正想分享的。spy 函數(shù)的基本功能就是提供對(duì)目標(biāo)函數(shù)的監(jiān)視,例如執(zhí)行次數(shù),出入?yún)⒌取R粋€(gè)函數(shù)在聲明后,JavaScript 無法讓我們二次修改它的內(nèi)容,因此通常來說 spy 庫會(huì)將原本函數(shù)的引用指向新的實(shí)現(xiàn)。一個(gè)簡單的 spy 函數(shù)可以是這樣:

它將object[method]指向了新的函數(shù),首先更新函數(shù)執(zhí)行的次數(shù)、記錄每次執(zhí)行的入?yún)?,然后?code>call執(zhí)行原始函數(shù)。

對(duì)應(yīng)到本文的例子中,當(dāng)我們聲明const spy = vi.spyOn(wrapper.vm, 'handleConfirm')后,wrapper.vm.handleConfirm就被指向了 spy 生成的新函數(shù),這個(gè)改動(dòng)針對(duì)的是 Vue 實(shí)例對(duì)象,而我們由模板編譯生成的渲染函數(shù)仍保持不變。因此const prop = { onClick: ctx.handleConfirm }onClick仍指向原始函數(shù)的引用,無論 handleConfirm 之后怎么改變,其在渲染函數(shù)生成后就從始至終不變了。而const prop = { onClick: ($event) => { ctx.handleConfirm() } }ctx.handleConfirm()會(huì)在點(diǎn)擊回調(diào)觸發(fā)后解析,此時(shí)就會(huì)指向spyOn定義的新函數(shù)了。

總結(jié)

當(dāng)搞清楚模板語法生成事件回調(diào)的邏輯后,我們就會(huì)發(fā)現(xiàn)這其實(shí)是一個(gè)經(jīng)典的對(duì)象引用指向的問題。受限于 JavaScript 語言特性,mock 行為實(shí)際上創(chuàng)建了一個(gè)新的函數(shù),而上下文若仍保持著對(duì)原函數(shù)的引用,那 mock 行為不會(huì)按照預(yù)期運(yùn)行也就可以理解了。

當(dāng)你想要測試組件是否正確地 emit,也許應(yīng)該嘗試@vue/test-utils中的emitted()方法。或者將視角拉得更高,從最終頁面呈現(xiàn)的內(nèi)容來判斷。

關(guān)于 OpenTiny

OpenTiny?是一套企業(yè)級(jí) Web 前端開發(fā)解決方案,提供跨端、跨框架、跨版本的?TinyVue 組件庫,包含基于 Angular+TypeScript 的?TinyNG 組件庫,擁有靈活擴(kuò)展的低代碼引擎?TinyEngine,具備主題配置系統(tǒng)TinyTheme?/ 中后臺(tái)模板?TinyPro/?TinyCLI?命令行等豐富的效率提升工具,可幫助開發(fā)者高效開發(fā) Web 應(yīng)用。

歡迎加入?OpenTiny 開源社區(qū)。添加微信小助手:opentiny-official 一起參與交流前端技術(shù)~更多視頻內(nèi)容也可關(guān)注B站、抖音、小紅書、視頻號(hào)

OpenTiny?也在持續(xù)招募貢獻(xiàn)者,歡迎一起共建

OpenTiny 官網(wǎng):https://opentiny.design/

OpenTiny 代碼倉庫:https://github.com/opentiny/

TinyVue 源碼:https://github.com/opentiny/tiny-vue

TinyEngine 源碼:?https://github.com/opentiny/tiny-engine

歡迎進(jìn)入代碼倉庫 Star??TinyEngine、TinyVue、TinyNG、TinyCLI~

如果你也想要共建,可以進(jìn)入代碼倉庫,找到?good first issue標(biāo)簽,一起參與開源貢獻(xiàn)~


為了 Vue 組件測試,你需要為每個(gè)事件綁定的方法加上括號(hào)嗎?的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國家法律
江口县| 襄城县| 甘谷县| 马山县| 鄂州市| 昌都县| 巧家县| 中江县| 修水县| 麻栗坡县| 靖宇县| 庆城县| 灌阳县| 正定县| 仁化县| 涞源县| 莲花县| 古交市| 泰兴市| 临漳县| 扶沟县| 西吉县| 玛纳斯县| 璧山县| 乾安县| 垣曲县| 洛阳市| 台东县| 寻甸| 元朗区| 南木林县| 姚安县| 保德县| 湖北省| 和林格尔县| 六枝特区| 延长县| 政和县| 抚顺县| 西和县| 仙居县|