吐槽一下 Vue3 的語法設計
共 7213字,需瀏覽 15分鐘
·
2024-07-18 08:45
很長一段時間以來,老有人私信跟我說,Vue 很先進,代表了未來,你不要沉迷在 React 這個年老色衰的技術棧里自娛自樂啦,睜開眼睛去看看世界吧,別的技術棧都發(fā)展早就比 React 更先進啦!!
然后這些人估計都不會相信,我真的有非常認真的去把 Vue 從頭到尾學了個遍,甚至 Vue2、Vue3 的原理我也理清楚了... 很多年前,我甚至還在我的付費小冊《JavaScript核心進階》里,聊設計模式的時候,分享一個 Vue2 簡易版的實現原理作為案例...
不僅如此,我還睜眼看了 rust 生態(tài)的 Leptos,看了 Solid.js,看了 Svelte,還看了鴻蒙開發(fā)的 ArkUI,看了 Android 開發(fā)的 compose,這些都是近些年新出的技術方案,我都有認真的學過奧
也不知道這些喊我睜眼看世界的,有沒有我看得多...
然后這篇文章,我就主要以吐槽 Vue 的語法設計為主,來聊一下我 Vue3 的學習和使用體會
一、ref 與 reactive
一個很不好的體驗就是 ref 與 reactive 都太容易丟失響應了。為了防止丟失響應,我需要隨時注意我的數據使用方式,我不能隨心所欲的按照 JavaScript 的基礎語法去任意妄為。
首先嚴格踐行語義化的我,第一反應是不想使用 ref。因為 ref 是 引用的縮寫,從語義上來說,他是不應該具備響應性的。但是偏偏 Vue3 的語法設計就沒這么講究,于是我的語義化思維,在我學習和使用響應式數據時給我造成了極大的困擾...
我剛開始在項目中,就偏好于使用 reactive。但是現實很快就把我的偏好捏碎了。比如下面這個例子,我將一個列表作為響應性數據定義在 reactive 中
但是我萬萬沒想到的是,這樣使用是有問題的。因為當我從接口里面獲得一個新數據的時候,想要直接用新的列表覆蓋初始列表,結果居然沒有什么好的辦法能讓這種覆蓋生效!!!
然后我就只能這么寫
我這個組件只有一個響應式數據的時候,就賊難受,所以我就想著法又加了一個,這樣就舒服一點了
這樣處理之后呢,我想著用的時候,就很自然的想著用解構語法來使用吧。但是呢,響應性又丟失了...
這樣寫不行
這樣也不行
必須要引入一個新的 api 來解決這個問題 toRefs
然后我就含淚看著我的 reactive 被強行變成了 ref. 這其實我還勉強可以接受,最令我崩潰的是,由于 list 和 open 都被轉化成 ref,因此使用的時候,我必須這樣用,把 .value 的尾巴加回來...
所以 reactive 一個符合語義的響應式 api,給我的使用感受就是,在設計上就是非常失敗。但是一個不太符合語義的的響應式 api ref 被處理得還相對好一些。在這樣的情況之下,也就不得不更多的在項目中使用 ref。
但是使用 ref 的時候,除了不符合語義化之外,還不符合一致性。因為在 script 中使用,我們必須加上 .value 來處理。但是呢,template 中又不用... 甚至如果我為了一致性在模板中用了還會出問題...
直接給我一直以來自認為良好的編碼標準干碎了...
所以我現在的用法是,使用 reactive,但是忍痛放棄解構,從而避免使用 toRefs。盡量避免使用 ref。雖然很多人發(fā)文章說官方強烈建議使用 ref,但是確實語義和一致性有點挑戰(zhàn)我的底線。當然我也知道他在能力上處理得更好一些。
二、傳參設計得真復雜
從一個新手的角度,要理解 Vue3 的參數傳遞,居然是一件學習成本非常高的事情。原因就是因為為了確保響應性和區(qū)分普通參數,這里又設計了許多新的 api 來解決問題
首先是參數的類型很復雜。
因為 Vue 中設計了一個指令系統(tǒng),用于處理一些條件渲染的邏輯。比如 v-if
等價于
但是這個機制就由此就導致了在父組件使用自定義組件時,往子組件傳參就變得非常復雜。因為在子組件內部就沒辦法統(tǒng)一接收屬性參數了。因為有的屬性呢,他是自定義指令,是不應該往下傳的,但是有的指令呢,又需要往下傳
例如事件回調
這個時候我們在學習的時候,就必須的保證區(qū)分如下幾種傳參
一種就是有特殊含義的內置指令或者自定義指令
一種是函數類型的參數,多為事件回調函數
一種是數據綁定類型的,這種被 Vue 官方成為動態(tài)類型。
還有一種就是正常的參數傳遞,這種被 Vue 官方文檔稱為靜態(tài)參數類型。
然后這里有一個很魔性的約定,如果你參數的 key 值使用橫杠的方式,如下
到子組件接收的時候,它居然強制把這個key 的寫法改了 ... ...
你得寫成這樣才能被接收
還有一個對我來說,誤導性更大的一種情況。就是當我試圖使用靜態(tài)參數類型傳遞一個靜態(tài)對象時,你猜怎么著?傳不了!
我只能改成動態(tài)的綁定寫法,才能正常傳遞。這里為啥誤導性很強呢,因為在我看來,雖然我聲明的是一個對象,但是他就是一個靜態(tài)的數據,也不是一個響應式數據
所以這個就給我干懵了。沒辦法,雖然我已經知道怎么用了,但是我到現在也不太確定官方文檔說的動態(tài)屬性表達的準確定義是啥。
然后完了之后呢,還有一種參數類型,叫做透傳 Attributes,比如像這種,他可以直接在內部元素貼上去生效
所以我個人的感受就是,不僅設計得復雜,還有一些我覺得不夠合理的地方。有人說,這個學習成本低,我是不太信的。
?但是我得說一下,這些,我都學會了,也知道怎么區(qū)分怎么用了,非常的熟練,難不到我。但不妨礙我不喜歡這樣的設計。
三、接收參數的迷惑行為
在子組件中,接收參數我最迷惑的一個行為是
當我這樣寫的時候,可以直接在 template 中使用 msg
但是在 <scirpt> 中就用不了... 不一致的表現讓我覺得非常難受。
然后另外一個讓我覺得非常難受的語法設計就是對于事件回調函數的處理。例如我想要通過 @click 傳遞一個回調函數到子組件,但是這個時候,子組件怎么接收這個回調函數呢?
他的接收邏輯,又跟 props 的邏輯完全不一樣了。
我認為的常規(guī)邏輯無非就是在父組件中,一個 key=value 的方式傳遞下去,然后在子組件中通過識別 key 來獲得這個 value,但是 Vue 又設計了一個新的思路,重新用了一個宏來處理這個事情
而且調用的邏輯我也覺得有點懵... 這是啥?連回調函數的執(zhí)行都不見了...
我寧愿這樣傳
這樣接收和使用,更符合我的一致性的想法。
四、watch 的怪異行為
在 watch 的時候,也有一個奇怪的行為。那就是當我使用 reactive 聲明了狀態(tài)時,偶爾想要某個屬性被 watch 一下,結果卻發(fā)現,普通的屬性值,居然不可以...
我必須額外提供一個 getter 函數才能做到
這又給我偏執(zhí)的想要使用 reactive 增加了不舒服的感覺... 難頂啊。當然有的人比較喜歡用 watchEffect ,但是這里有一個非常重要的問題,就是他會自動追蹤所有能訪問到的響應式屬性。很明顯這是一種簡單粗暴,并且也存在冗余監(jiān)聽風險的一種方式。他的響應性依賴關系并不明確,所以我并不是很喜歡使用它。
五、總結
很顯然, Vue3 為了底層的 Proxy 實現原理,在逐步放棄虛擬 DOM 的過程中,在語法設計上做了非常多的犧牲和妥協,它為了解決數據響應性丟失的問題,新增了許多的 api。因為很多東西是不得不這么處理,否則能力上就會存在問題。所以很多人在說,React 為什么不擁抱 Signal,難道你真的認為,擁抱了 Signal,就不會做出任何犧牲嗎?全是正向收益?
別做夢了!不可能的!哪怕是 Solid.js 這種沒有歷史負擔,重新設計的類 React 框架,在響應性的丟失上也備受困擾,怎么可能那么簡單就全是正向的收益?
無論是從語法設計的角度來考慮,還是從設計模式的方向來考慮,基于類似 signal 的底層實現,語法表現上明顯更適合設計為面向對象。我們可以基于裝飾器和依賴注入來完整底層邏輯的設想,例如
?如果能通過解析省掉 script 和 template 標簽就更好了
這樣設計之后,就完全不需要擔心任何響應性丟失的問題。從而極大的降低了學習成本和使用心智負擔。深度使用之后給我整體感受就是,Vue3 擁抱函數式,擁抱得很勉強。一方面是上手難度提高了,另外一方面是使用過程中的心智負擔也變重了。所以,從 Vue2 切換到 Vue3,絕非有的人認為的那么平滑,甚至可以說是重新學了另外一個框架。甚至我認為,React 開發(fā)者到 Vue3 才是平滑的切換,他們比 Vue2 開發(fā)者更容易接受 Vue3. 并且一個很有意思的事情是,如果你要學習 Vue3 的最佳實踐,我這篇寫給 React 開發(fā)者的文章,反而完美的契合了 Vue3 的使用思考。
除此之外,由于 Vue2 發(fā)展得非常成熟,所以哪怕 Vue3 已經發(fā)布了四周年了,Vue3 的學習資料也經常和 Vue2 混雜在一起,它的被接受程度遠低于預期。
也許越往后發(fā)展,angular 更有機會重新大放異彩。畢竟他的底層邏輯和上層表現是一脈相承的,有比較扎實的設計理論基礎,angular 在保持現有開發(fā)方式不變的情況下,擁抱 signal 非常的自然。
只能說,自定義 hook 這種的邏輯復用的方式,和面向對象中的繼承、注入、mixin 等方式相比,確實在易用性和可讀性上的優(yōu)勢太明顯了,因此函數式才這么受歡迎,大多數新的前端框架都在這個模式下實現自己的理念... 但是在語法層面,React 依然是邏輯最自恰的。
