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

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