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