Vue3又出新語(yǔ)法 到底何時(shí)才能折騰完?
前言
大家應(yīng)該知道如果用 Vue3 的 Composition API 定義一個(gè)響應(yīng)式變量通常有兩種形式,一種是用ref,另一種是reactive:
- <script setup>
- import { ref, reactive } from 'vue'
- const isLoading = ref(true)
- const user = reactive({
- name: '令狐沖',
- age: 22,
- gender: '男'
- })
- </script>
一般來(lái)說(shuō)定義一個(gè)基本數(shù)據(jù)類型會(huì)用ref,而引用類型則會(huì)采用reactive,那么問(wèn)題來(lái)了,ref雖然定義了一個(gè)基本數(shù)據(jù)類型,但實(shí)際上它卻是一個(gè)引用類型,取值和賦值時(shí)必須要帶上.value屬性:
- <script setup>
- import { ref } from 'vue'
- const isLoading = ref(true)
- if (isLoading.value) {
- isLoading.value = false
- }
- </script>
這就有點(diǎn)不太符合直覺了,很有可能一不小心就被寫成了這樣:
- <script setup>
- import { ref } from 'vue'
- let isLoading = ref(true)
- if (isLoading) {
- isLoading = false
- }
- </script>
這要是有TS和ESLint的加持還好,要是沒有的話可就不好找錯(cuò)誤了,也不會(huì)產(chǎn)生什么有用的報(bào)錯(cuò)信息,而且每次都要帶上這個(gè).value實(shí)在是不好看,而且寫起來(lái)也麻煩呀!
reactive的弊端是不能解構(gòu),解構(gòu)就會(huì)失去響應(yīng)性:
- <script setup>
- import { reactive } from 'vue'
- const user = reactive({
- name: '令狐沖',
- age: 22,
- gender: '男'
- })
- // 這種寫法通常達(dá)不到預(yù)期的效果
- let { age } = user
- age = 18
- </script>
可能有人會(huì)說(shuō),不是有toRefs嗎?用了toRefs,就又會(huì)回到那個(gè).value的問(wèn)題上了:
- <script setup>
- import { reactive, toRefs } from 'vue'
- const user = reactive({
- name: '令狐沖',
- age: 22,
- gender: '男'
- })
- let { age } = toRefs(user)
- age.value = 18
- </script>
其實(shí)我個(gè)人覺得還好啦,因?yàn)橐呀?jīng)寫習(xí)慣了,再加上一直用TS有提示和自動(dòng)補(bǔ)全,所以感覺沒什么問(wèn)題。
但知乎上類似于《為什么 vue3 刪不掉 ref() 這樣冗余的函數(shù),但 svelte 可以?》這種問(wèn)題深深的刺痛了大佬的內(nèi)心,大佬自己的強(qiáng)迫癥也犯了,畢竟他當(dāng)年創(chuàng)造Vue的最成功要素之一就是方便。而如今這種冗余的寫法卻與方便毫不搭邊兒,所以尤大無(wú)論如何也必須要解決這個(gè)問(wèn)題,不能讓人背后嚼耳根子說(shuō)Vue寫起來(lái)還沒Svelte方便是不是?于是乎大佬先后創(chuàng)建了三次不同的語(yǔ)法糖,它們分別是:
- 《[譯]尤雨溪: Ref語(yǔ)法糖提案》
- 《Vue第二波ref語(yǔ)法提案來(lái)襲 這次會(huì)進(jìn)入到標(biāo)準(zhǔn)嗎?》
- 本文 (第二波語(yǔ)法糖的修改版)
我們先來(lái)簡(jiǎn)單的看一下,這三次語(yǔ)法糖的寫法:
第一波語(yǔ)法糖
第一波主要是模仿了Svelte的寫法,我們先來(lái)看看Svelte的中文官網(wǎng)給出來(lái)的一段例子:
- <script>
- export let title;
- // 這將在“title”的prop屬性更改時(shí)更新“document.title”
- $: document.title = title;
- $: {
- console.log(`multiple statements can be combined`);
- console.log(`the current title is ${title}`);
- }
- </script>
這個(gè)$:是一種叫做label的語(yǔ)法,這種語(yǔ)法并不是Svelte自創(chuàng)的語(yǔ)法,而是一種長(zhǎng)期在被廢棄的邊緣上瘋狂試探的合法語(yǔ)法,只不過(guò)這種語(yǔ)法原本并不是這么用的,人家是用在嵌套循環(huán)上的:
- let num = 0
- outermost:
- for (let i = 0; i < 10; i++) {
- for (let j = 0; j < 10; j++) {
- if (i == 5 && j == 5) {
- continue outermost
- } else {
- console.log(i, j, 88)
- }
- num++
- }
- }
- console.log(num) //95
看不懂沒關(guān)系啊,也沒必要去弄懂這種語(yǔ)法,因?yàn)樗粔蛑庇^,用處也不是很大,所以幾乎沒什么人用它!我在編輯器寫這段代碼的時(shí)候 ESLint 都直報(bào)錯(cuò):

翻譯:Label語(yǔ)法源于GOTO語(yǔ)句,使用它將會(huì)令代碼變得難以理解、難以維護(hù)。—ESLint
不過(guò)既然沒什么人在用,同時(shí)它還是JS的合法語(yǔ)法,那用它來(lái)告訴編譯器這里是聲明了一個(gè)ref變量豈不是很完美?于是乎尤大也搞了個(gè)和Svelte類似的語(yǔ)法:
- <script setup>
- ref: isLoading = true
- if (isLoading) {
- isLoading = false
- }
- </script>
那么大家為何會(huì)如此反對(duì)呢?就是因?yàn)閘abel語(yǔ)法壓根兒就不是這么用的,人家原本是為了和break、continue配合使用的,雖然在別的地方用也不算是語(yǔ)法錯(cuò)誤,但你這么做明顯是修改了JS原本的語(yǔ)意!雖然尤大表示很不服啊:為什么Svelte用這玩意你們都沒說(shuō)啥,我一用這玩意你們就開噴?!
個(gè)人感覺是因?yàn)镾velte從一開始就說(shuō)自己是一個(gè)編譯器,沒有沉重的歷史包袱,而Vue卻恰恰相反。而且Svelte本身也不是什么主流框架,屬于給那幫愛折騰的人玩的。但Vue不一樣,已經(jīng)有多少人要靠著Vue吃飯呢,并不是所有人都那么愛折騰的。
于是在萬(wàn)般無(wú)奈之下,尤大只好放棄了這個(gè)提案,但這件事在尤大心里始終還是揮之不去、如鯁在喉,于是乎他吸取了第一波語(yǔ)法糖的教學(xué),卷頭重來(lái)又起草了一份新提案:
第二波語(yǔ)法糖
- <script setup>
- let loading = $ref(true)
- if (loading) {
- loading = false
- }
- </script>
可以看到我們并沒有引入$ref這個(gè)變量,這個(gè)變量是從哪來(lái)的的呢?是只要在<script>標(biāo)簽里寫了setup這個(gè)屬性就會(huì)自動(dòng)注入的一個(gè)全局變量(需要先開啟實(shí)驗(yàn)性語(yǔ)法開關(guān))
尤大心想:你們不是嫌我之前用了不規(guī)范的語(yǔ)法么?那我這回這么寫應(yīng)該沒問(wèn)題了吧!想想之前我們定義一個(gè)ref變量,首先需要先把ref引進(jìn)來(lái)然后才能用:
- import { ref } from 'vue'
- const loading = ref(true)
而新語(yǔ)法不用引,直接就能用,類似于全局變量的感覺。除了$ref這個(gè)特殊的全局變量呢,這次提案還有:$computed、$fromRefs和$raw這幾個(gè)玩意。我們一個(gè)個(gè)來(lái)看,先看$computed:
- <!-- 以前 -->
- <script setup>
- import { ref, computed } from 'vue'
- const num = ref(1)
- const num_10 = computed(() => num.value * 10)
- </script>
- <!-- 現(xiàn)在 -->
- <script setup>
- let num = $ref(1)
- const num_10 = $computed(() => num * 10)
- </script>
$fromRefs又是個(gè)啥呢?這玩意在之前沒有啊!只聽說(shuō)過(guò)toRefs:
- <script setup>
- import { fromRefs } from 'vue' // 這個(gè)API并不存在
- import { toRefs } from 'vue' // 這個(gè)API倒是有 也就是只有 to 沒有 from
- </script>
其實(shí)這個(gè)$fromRefs正是為了配合toRefs而產(chǎn)生的,比方說(shuō)我們?cè)趧e的地方寫了一個(gè)useXxx:
- import { reactive } from 'vue'
- const state = reactive({
- x: 0,
- y: 0
- })
- export default = (x = 0, y = 0) => {
- state.x = x
- state.y = y
- return toRefs(state)
- }
于是我們?cè)谑褂玫臅r(shí)候就:
- <script setup>
- import { useXxx } form '../useXxx.js'
- const { x, y } = useXxx(100, 200)
- console.log(x.value, y.value)
- </script>
這豈不是又要出現(xiàn)尤大最不想看到的.value屬性了嗎?所以$fromRefs就是為了解決這個(gè)問(wèn)題而生的:
- <script setup>
- import { useXxx } form '../useXxx.js'
- const { x, y } = $fromRefs(useXxx(100, 200))
- console.log(x, y)
- </script>
最后一個(gè) API 就是$raw了,raw 不是原始的意思嘛!那么看名字也能猜到,就是我們用$ref所創(chuàng)建出來(lái)的其實(shí)是一個(gè)響應(yīng)式對(duì)象,而不是一個(gè)基本數(shù)據(jù)類型,但語(yǔ)法糖會(huì)讓我們?cè)谑褂玫倪^(guò)程中像是在用基本數(shù)據(jù)類型那樣可以改來(lái)改去,但有時(shí)我們想看看這個(gè)對(duì)象長(zhǎng)什么樣,那么我們就需要用到$raw了:
- <script setup>
- const loading = $ref(true)
- console.log(loading) // 其實(shí)打印的不是 loading 這個(gè)對(duì)象 而是它里面的值 相當(dāng)于 loading.value
- console.log($raw(loading)) // 這回打印的就是 loading 這個(gè)對(duì)象了
- </script>
改進(jìn)版
這一版語(yǔ)法糖沒過(guò)多久就又被改進(jìn)了,改進(jìn)版主要是把全局變量改為只有$和$$這倆變量了,假如我們不用語(yǔ)法糖時(shí)是這么寫:
- <script setup>
- import { ref } from 'vue'
- const loading = ref(true)
- console.log(loading.value)
- </script>
用語(yǔ)法糖以后就變成了這樣:
- <script setup>
- import { ref } from 'vue'
- const loading = $(ref(true))
- console.log(loading)
- </script>
如果我們想還原 loading 這個(gè)變量,我們就需要用到$$了:
- <script setup>
- import { ref } from 'vue'
- let loading = $(ref(true))
- console.log($$(loading))
- </script>
或者也可以寫成這樣:
- <script setup>
- import { ref } from 'vue'
- const loadingRef = ref(true)
- let loading = $(loadingRef)
- console.log(loadingRef === $$(loading))
- </script>
第三波語(yǔ)法糖
第三波語(yǔ)法糖主要是在第二波語(yǔ)法的基礎(chǔ)上又進(jìn)行了改進(jìn),除了許多人覺得要寫成$(ref())的話實(shí)在是太那什么了…
另一方面則是實(shí)現(xiàn)了props的語(yǔ)法糖,新的語(yǔ)法主要是為每個(gè)能夠創(chuàng)建帶有.value變量的方法都有一個(gè)$前綴的等價(jià)物,比如:
- ref
- computed
- shallowRef
- customRef
- toRef
與此同時(shí)保留了改進(jìn)版中的$變量與$$變量,用于對(duì)props的解構(gòu):
- <script setup>
- const { isLoading } = $(defineProps({ isLoading: Boolean }))
- </script>
要知道在以前我們是不能對(duì)props進(jìn)行解構(gòu)的,而現(xiàn)在還可以利用ES6的解構(gòu)默認(rèn)值寫法來(lái)為props設(shè)置默認(rèn)值:
- <!-- 以前 -->
- <script setup>
- const props = defineProps({
- isLoading: {
- type: Boolean,
- default: true
- }
- }))
- console.log(props.isLoading)
- </script>
- <!-- 現(xiàn)在 -->
- <script setup>
- const { isLoading = true } = $(defineProps({ isLoading: Boolean }))
- console.log(isLoading)
- </script>
三波語(yǔ)法糖提案地址
- 第一波:github.com/vuejs/rfcs/…
- 第二波:github.com/vuejs/rfcs/…
- 第三波:github.com/vuejs/rfcs/…
這個(gè)框架明明是中國(guó)人用的最多,但可笑的是居然是一群外國(guó)人在商量Vue的下一步計(jì)劃,看到這里肯定有人會(huì)說(shuō):中國(guó)人都忙著996呢,哪有空去探討那些東西…
那就看你是覺得:這些亂七八糟的語(yǔ)法糖對(duì)你來(lái)說(shuō)無(wú)所謂,出什么語(yǔ)法我學(xué)什么就是了,我就是一只沉默的羔羊。
還是說(shuō):你只是在這篇文章的下面留個(gè)言說(shuō)自己喜歡這些新語(yǔ)法或者討厭這些新語(yǔ)法,懶得去GitHub說(shuō)英文。
鏈接已經(jīng)給大家貼上來(lái)了,就看大家是一副湊熱鬧的態(tài)度,還是點(diǎn)進(jìn)去鏈接勇敢的表達(dá)出自己的聲音了。當(dāng)然,如果去GitHub我們還是要說(shuō)英文的,雖說(shuō)用中文的話尤大也可以看得懂,但評(píng)論區(qū)不全是中國(guó)人,Vue還是有相當(dāng)一批外國(guó)粉絲的。而且也不全是美國(guó)人,那些不是英國(guó)人美國(guó)人的開發(fā)者,他們?nèi)绻仓粓D自己痛快而說(shuō)自己國(guó)家的母語(yǔ)的話,想必我們就沒有辦法進(jìn)行溝通了,同時(shí)這也會(huì)進(jìn)一步拉近國(guó)人在海外的形象:別人都用英文,就你們中國(guó)人用自己的語(yǔ)言,不遵守規(guī)則。
那可能有人英文水平真的很差,我們可以這樣嘛:找到百度翻譯,輸入中文后翻譯成英文,然后再把英文復(fù)制過(guò)去。雖然這樣做翻譯的可能不完全準(zhǔn)確,但最起碼能達(dá)到勉強(qiáng)看懂的地步。同時(shí)還有一個(gè)技巧就是把翻譯成英文的句子再翻譯回中文,看看有哪些地方的語(yǔ)意發(fā)生了明顯的變化,我們?cè)籴槍?duì)那個(gè)地方重新自己寫一遍。
如果你喜歡這個(gè)語(yǔ)法,那就去多點(diǎn)幾個(gè)贊多夸幾句,這樣的話想必它很快就會(huì)被納入到Vue的標(biāo)準(zhǔn)語(yǔ)法里面去。
如果你不喜歡,那么就趕快去多噴幾句,這樣的話這個(gè)語(yǔ)法很有可能就會(huì)像第一波語(yǔ)法糖提案那樣被放棄掉了。





























