近日,VUE 作者尤雨溪在社區意見徵求稿(RFC) 上提交了一份 Ref 語法糖的提案,引發了社區的爭議。
簡要地說,這份提案在單文件組件(SFC)中引入了一個新的 script 標籤寫法,寫法為 <script setup>
,這種寫法會自動將所有頂級變量聲明暴露給模板(template)使用。同時在 <script setup>
中引入了一個消除 ref 的 value 屬性的語法糖,該語法糖在編譯期間自動將語法糖轉為正常代碼。
例如這樣的 HTML 代碼:
<script setup>// 聲明一個會被編譯到 ref 的變量ref: count = 1function inc() { // 該變量可像普通的值一樣使用,無需 .value count++}// 用 $ 前綴對應原始的 ref 對象console.log($count.value)</script><template> <button @click="inc">{{ count }}</button></templat
就會被編譯成這樣:
<script setup>import { ref } from 'vue'export default { setup() { const count = ref(1) function inc() { count.value++ } console.log(count.value) return { count, inc } }}</script><template> <button @click="inc">{{ count }}</button></templ
注意 label: x = 1 仍是有效的 JS 語法(即帶標籤的賦值語句,相當於直接對 x 賦值,嚴格模式下如果 x 未定義則會報錯),相當於特殊處理了 ref 標籤名的語義。
尤雨溪表示,一方面是通過自動暴露頂級變量可以減少代碼的冗餘度;另一方面,通過 ref:
語法可以讓 ref 更高效。
自從 Composition API 誕生以來,大家就一直在討論到底用 ref 好還是用 reactive 對象好。用 ref 會導致代碼中到處都是 .value
,而且如果開發者沒有使用 TypeScript,還會經常漏寫 .value
,所以很多開發者不知道該怎麼處理 ref。
實際上 ref 的存在是因為 JS 這門語言存在一些問題:如果不把一個值封裝在一個對象裡,就無法通過 JS 自帶的方法把這個值變成一個響應式的數據。這意味著如果不魔改 JS 的語義,就不可能像是用普通變量那樣使用 ref。此外,尤雨溪也承認自己是從 Svelte 框架得到了啟發,Svelte 早前就魔改了 JS 的語義, export
let
$
的語義都被 Svelte 賦予了更強大更方便的功能。
「以前我們希望儘量維持 JS 原本的語義不變,因為魔改 JS 會帶來一些問題,但是總需要有「第一個吃螃蟹」的人,對 JS 進行改造,給開發者提供更好的開發體驗。」尤雨溪說。
針對這一提案,很多社區用戶留言表示反對,大多數人反對的原因還是集中在這一提案對 JavaScript 魔改太嚴重 ,質疑為什麼用標籤語法而不是直接發明新語法,讓用戶的心智負擔加重了等等。
針對這些質疑,尤雨溪本人也一一做出了回應:
關於魔改 JavaScript
ref: count = 1 使用的是標籤語法,在 syntax 層面是合法的 JavaScript,而且在非嚴格模式下是可以正常執行的,甚至語義也是聲明了一個名為 count 的全局變量。同時這也是合法的 TypeScript 語法,不會和類型聲明混淆(類型聲明必然需要 let 和 const)
當然這裡確實只是語法層面的合法,實際上等於是給
ref:
這個標籤賦予了一個不同的語義。標籤語法本身是一個極少被使用的功能,實際使用也都是用於標記循環聲明(用在 for/while 前面),像例子中ref: count = 1
這樣的用法,其原始語義是毫無用處的,這也是為什麼我們認為犧牲這個原始語義來獲得響應式的變量聲明是一個值得的交換。
為什麼用標籤語法而不是直接發明新語法
使用標籤語法確實是受到了 Svelte 的啟發。根本原因在於和 JS 保持 syntax 層面的完全兼容能夠儘可能保證現有的 JS 工具生態對接。標籤語法能夠正確地被 Babel,TS parser/transformer(如 esbuild/swc),Prettier,ESLint 以及任何 IDE 的 JavaScript 語法高亮所直接支持,只有在涉及語義的情況下,如類型推導和 ESLint 變量相關規則才需要針對性的兼容。如果用一個全新的非標準語法,就意味著需要在 parser 層面對上述所有工具進行修改,基本不可行。
感覺心智負擔變重了
雖然底層是編譯到 ref() 的語法糖,但其實對於新人來說根本不需要知道 ref() 的存在就可以使用,因為在不需要獲取底層 ref 對象的場景下,通過 ref: 聲明的變量心智模型和用 let 聲明的變量的心智模型完全一致。用戶只需要把 ref: 當成一個響應式的 let 就行了。這個模型已經足夠實現大部分入門級別的功能,只有到進階之後開始學習邏輯抽取復用時,才需要知道 ref() 的概念。
對於已經學習了 Composition API 的用戶來說會覺得 「又多了一個概念」,同時由於 RFC 事無巨細地討論了編譯的規則,會產生一種 「心智負擔增加了」 的錯覺。其實我很久以前用 CoffeeScript,Babel,或是剛開始用 TS 的時候,也有這樣的感覺,因為我喜歡用之前先看看這東西編譯出來是個什麼樣子。結果就是看過了這個之後用著上層語法,腦子裡忍不住去把它轉換成底層語法。但這本質上是我們的大腦在習慣了底層思維方式之後的一種慣性。這種慣性在使用新語法一段時間之後很快就消失了,我們的大腦適應能力還是很強的。如果你開始就不 care 編譯出來是個什麼結果,就更不會有這個問題(你用 nullish coalescing 或者 decorator 的時候會去想著 babel/TS 編譯出來是個什麼結果麼?)
對於從零開始的用戶來說,如上所述 ref: 就是一個能觸發響應的 let 而已,學習成本是很低的。
尤雨溪回答原文:https://www.zhihu.com/question/429036806/answer/1564223482
最後,尤雨溪表示在這個 RFC 提案發表之前自己就知道會引起很多爭議,也非常理解人們面對新技術的第一反應是「不能接受」。不過他也表示提案未必就會實裝,希望大家討論的時候保持理性,同時建議大家完整看完 RFC 全文和 GitHub 上的討論後再提出質疑。
完整 RFC :https://github.com/vuejs/rfcs/blob/script-setup/active-rfcs/0000-script-setup.md