藍瘦,想哭
我最近在死磕Vue 3的組合式API,說實話,雖然道理都懂,但寫起來卻非常難受。
藍瘦主要表現有這么幾點:
一、代碼不像Vue 2那樣被約束,寫起來心累怎么辦
尤雨溪觀點:
首先說尤雨溪完全清楚這個問題,也承認有這個問題,選項式API有天然的代碼約束,而組合式API沒有天然的代碼約束,但是他依然認為組合式API才是未來,尤雨溪支持組合式API的主要理由是,在大型的應用開發里,邏輯只有和用到的數據放在一起才更容易維護,這叫做“關注點分離”,這樣可以提升可維護性,可維護性大于一切,而且不再有反復橫跳現象。
我的觀點:
我的觀點絕對代表了一大批人的意見,我認為,這兩種模式各有利弊,既然都說組合式API是未來,那么我主要說說組合式API的弊端。
- 關于大型應用
如果大型應用的一個文件有幾千行代碼,那么你為什么不早早地分拆組件、拆分hook呢?在文件代碼行數較少的前提下,我認為組合式API的優勢并不明顯。
- 關于反復橫跳
反復橫跳確實存在,我認為橫跳是一定會有的,誰找代碼不是滾動滾輪到處找?選項式API是大橫跳,組合式API是小橫跳,都要跳。選項式API是滾動距離長,組合式API是滾動距離短,但是滾動距離長不代表用時長,畢竟滾動時間+人腦反應時間才是全部時間,選項式API的人腦反應時間并不長,有時還更短。只要你對代碼區塊位置形成固化記憶,并且用心給變量命名,這都不是問題。
對于代碼區塊位置形成固化記憶這個事,選項式API其實是有優勢的,就是說,選項式API最大的特點就是“選項”,你可以把“選項”視為索引,根據索引去找代碼,在遵循官方編寫建議的前提下,你腦中已經明白,computed屬性一定寫在data屬性的下方,watch屬性又在computed屬性的下方,methods屬性又在watch屬性的下方,生命周期夾在watch和methods之間,這些都相當于第一級分支,它們的屬性是第二級分支。你對你要找的代碼的大概位置很清楚。樹狀結構的終端就是各個變量,computed下面的終端變量一定是計算屬性,一定確定以及肯定,無心智負擔。
組合式API就不容易分這么細,畢竟它沒有第一級分支的約束,比如你在2個data之間夾一個computed是完全可以的,這時你為了減少找代碼的心智負擔,你就總要整理各個變量的書寫順序,結果,就又成了選項式API的組織形式。另外,組合式API的最左側只能看到一個個的let和const,然后你會看到變量名,從變量名,你無法一眼看出它是data還是computed,所以你的視線只能繼續右移,看變量名后面的代碼有沒有computed
字樣,而選項式API,早在選第一級分支的時候我就看過了computed,它下面的一定是計算屬性,毫無疑問。
對于用心給變量命名這個事,我意思是,請你讓自己遵循比較嚴苛的命名法則,比如英文單詞的單復數要敏感,事件函數寫成onChangeXX
、onClickOO
的格式,例如我給<select>元素寫事件,我肯定是寫onChangeXX
,我并不需要橫跳確認。在遵守足夠規則的前提下,你完全可以憑簡單記憶就寫出變量名和方法名,并不需要橫跳去查變量名。
- 關于選項式API的無盡的
this
組合式API一出,一些人驚呼,終于擺脫無盡的this
了!
我拜托,this這個關鍵字,從誕生以來就是為了簡便開發,也不是JS獨有,如果覺得this是“壞的”,倒不如說“編程語言都是垃圾”算了。
在選項式API里,我們只需要使用箭頭函數,保證this始終指向Vue組件實例就好了,組件內數據方法皆在this下,我不知道這有什么不好。我不想多解釋了。
- 關于組合式API的hook解決了選項式API的mixins的痛點
這話確實沒錯,如果你的主組件需要分拆,mixins又多、又需要通信,那么,至少這個主組件是強烈推薦使用組合式API,哪怕你并不喜歡組合式API。
二、組合式API代碼混亂怎么辦
目前我的解決方案是使用2種范式。
范式一:采用hook
利用hook,你可以將全體代碼拆分為若干個邏輯關注點,每個關注點寫成一個hook,然后你可以有2種方式存放hook:
- 第一種方式,在主文件里拆分成幾個hook函數。
- 第二種方式,主文件旁邊建立若干個hook文件。
這時候,原本要return 20個變量,現在大約是拆成了3個hook,每個hook里return 7個變量,主文件會return 3個變量,這樣可維護性更高。
范式二:主體用選項式API,setup()只引入和return hook,不處理邏輯
這是范式一的升級,是我目前采用的范式,至少2021年內我會始終采用這種范式,這種范式的優勢是,避免Mixins的缺陷,也避免組合式API代碼需要刻意維護代碼區塊的問題。
舉個例子,先編一個hook文件:
import { ref } from "@vue/reactivity";
function hook1 () {
let a = ref(false);
function changeA() {
a.value = !a.value;
}
return {a, changeA}
}
export default hook1;
主文件按照選項式API編寫,setup()只負責返回hook,其他沿用Vue 2的編寫方式:
import Hook1 from "../hooks/hook1";
export default {
setup() {
let useHook1 = Hook1();
let useHook1Other = Hook1();
return { useHook1, useHook1Other };
},
mounted() {
this.useHook1.changeA();
console.log(this.useHook1.a.value); // true
this.useHook1Other.changeA();
console.log(this.useHook1Other.a.value); // true
},
};
同時,<template>里的變量由于有useHook1.
、useHook1Other.
前綴,也就形成了模塊化。
三、定義一個響應式變量的時候,總會忘記引入vue方法怎么辦
解決辦法是,當你寫完let a = ref
,雖然ref
只有3個字母,并不需要聯想輸入,但你此時依然應該敲一下Tab鍵,vetur插件會幫你創建import { ref } from 'vue'
。reactive
等同理。你并不需要提前寫import { } from 'vue'
。
四、總忘記在return里添加變量怎么辦
并沒有什么快捷方法讓return {}
中自動出現a,
,只能手寫。
五、return的變量太多,密密麻麻怎么辦
確實,選項式API并不需要任何return,而且變量存放在各個分支(data、computed等)上,但是組合式API就需要return,而且是所有模板用得到的變量都需要return,導致data、computed、method全混在一起。
解決辦法是:
可以使用hook,使用之后,每個return的變量個數呈幾何級減少,這是比較好的解決辦法。
另外,可以使用<script setup>,免去return,但是你要放棄選項式API。
六、總是忘記寫ref對象的.value
怎么辦
可以使用<script setup>和ref:
標簽,但是你要放棄選項式API。
七、使用<script setup>時總忘記用ref:
標簽怎么辦
那沒辦法,習慣就好??傊?,.value
和ref:
你總要選擇一樣,看你的個人習慣吧。
八、解構導致失去響應式怎么辦
用toRefs。
const {a,b,c} = toRefs(Proxy對象)
解決辦法倒是簡單,主要是要形成肌肉記憶,不要忘記。
九、對引用類型數據不知道用ref還是用reactive怎么辦
如果后續打算重賦值變量,還不丟失響應式,那么用ref,如果后續只修改屬性,那么用reactive。不確定未來會怎樣的,先用reactive,未來再改。
總結:到底應不應該用組合式API,以什么原則定
盡管組合式API更適合大型項目,但是你不要等到大型項目才開始接觸Vue 3,那時候你真的會欲仙欲死,我建議你現在就開始學組合式API。
你的第一個Vue 3上線項目應該使用選項式API,這樣風險最低。之后可以將代碼逐步重構成組合式API。在這個過程中,你就會有感悟,到底是組合式API還是選項式API更好?其實你可以根據每個組件的特點來定。
拆分、復用是一門學問,要仔細體會。
不要一味否認另一套API,就像我有時候也用函數式組件,也偶爾用JSX,該用就用。我建議你主打其中一種,副打另一種,不要一味只使用一種,在一個項目內出現兩種API寫法是正常的做法,在一個組件里出現兩種寫法也沒問題,都是我推薦的做法。
選項式API有它某些不可比擬的優勢,尤雨溪不至于傻到幾個版本之后徹底取消選項式API,所以,如果你打算主打選項式API,副打組合式API,有時候還混用,我認為沒問題,就算幾個大版本之后,選項式API消失了,也依然會有高手寫出一個選項式API插件讓你用,放心吧。如果到時候真的沒有這種插件,說明選項式API確實是過時了,那時候相信你也早已習慣了組合式API,并且組合式API那時候已經有了更好的開發范式。