[譯]尤雨溪: Ref語法糖提案

語言: CN / TW / HK

前言

最近 ref 的語法糖引起了極大的爭議,很多人也是沒看 RFC 就直接開噴,雖然我也不喜歡這種語法,但還是有必要讓大家看一看在 GitHub 上的提案,看看國外開發者們普遍都是些什麼態度,是否和我們持有同樣的觀點,提案是 RFC 228,但 RFC 228 其實是為了方便與另一個提案區分開而新開的,原提案是 RFC 222,它最終被分解成了 RFC 227RFC 228,所以咱們先從 RFC 222 開始翻譯。

譯文

尤雨溪

簡介

  • 在單檔案元件(xxx.vue)中引入了一個新的 script 型別:<script setup> ,這種寫法會自動將所有頂級變數宣告暴露給 <template> 使用。
  • 介紹一個基於編譯器的語法糖,這種語法糖可以在<script setup>中讓你的 ref 不用再寫 .value 屬性了。
  • 注意,本提案意在代替 RFC 182 提出的 <script setup> 寫法。

基本用法

  1. <script setup> 直接向 <template> 暴露頂級變數

⚠️譯者注: 頂級變數就是沒宣告在塊級作用域裡面的變數

<script setup>
// 引入的 Foo 元件可以直接在 template 裡使用了!
import Foo from './Foo.vue'
  
import { ref } from 'vue'

// 就像在普通的 setup() 中一樣編寫 Composition API 程式碼,
// 無需手動返回所有內容
const count = ref(0)
const inc = () => { count.value++ }
</script>

<template>
  <Foo :count="count" @click="inc" />
</template>
複製程式碼

👆上面的這段程式碼將會編譯成下面這樣👇:

<script setup>
import Foo from './Foo.vue'
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(1)
    const inc = () => { count.value++ }

    return {
      Foo, // 即使 Foo 元件不是被宣告的,也會把它算作頂級變數
      count,
      inc
    }
  }
}
</script>

<template>
  <Foo :count="count" @click="inc" />
</template>
複製程式碼
  1. ref:語法糖令程式碼更簡潔
<script setup>
// 宣告一個變數(這個變數將會被編譯成一個ref)
ref: count = 1

function inc() {
  // 該變數可以像普通變數那樣使用
  count++
}

// 想要獲取到原本的變數的話需要在變數前面加一個💲符號
console.log($count.value)
</script>

<template>
  <button @click="inc">{{ count }}</button>
</template>
複製程式碼

👆上面的這段程式碼將會編譯成下面這樣👇:

<script setup>
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(1)

    function inc() {
      count.value++
    }

    console.log(count.value)

    return {
      count,
      inc
    }
  }
}
</script>

<template>
  <button @click="inc">{{ count }}</button>
</template>
複製程式碼

評論之前:

  • 請確保已經讀完完整的 RFC
  • 請不要簡單地回答"我喜歡/我不喜歡"-它不會對討論做出有意義的貢獻。
  • 如果不贊成該提案,請在動機弊端中提出的觀點範圍內進行具體論證。請注意,這中語法是 JavaScript 中的標籤語法。我們只是給 ref: 這個標籤提供了不同的語義。這就像在 HTML 寫 Vue 指令一樣。

⚠️譯者注:然後接下來是高贊回覆:

耶姆賈森(ycmjason 🇬🇧)

只是一個意見:

真的不喜歡這個主意。創造的語法太多了。而且這已經不再是 JavaScript 了。

尤雨溪(回覆)

你這種反應在我們的意料之中,我知道這可能會引起爭議,因此對於其他任何評論:

  • 請確保已經讀完完整的 RFC
  • 請不要簡單地回答"我喜歡/我不喜歡"-它不會對討論做出有意義的貢獻。
  • 如果不贊成該提案,請在動機弊端中提出的觀點範圍內進行具體論證。

私人號碼(privatenumber 🇺🇸 🇯🇵)

編譯器會自動將所有匯入的 xxx.vue 檔案註冊為元件嗎?

具體來說,我很好奇編譯器怎麼識別什麼是Vue元件,什麼不是。如果我匯入一個 xxx.js 形式的元件,那還可以使用嗎?

如果我們用到了高階元件的話,也可能不希望這個匯入的高階元件被自動註冊成元件。

如果我們要新增自定義語法,那麼 ES2021 的 export default from 語法是否可以實施?

我覺得這種寫法可能既簡潔又明確:

export { default as Foo } from './Foo.vue';
複製程式碼

尤雨溪(回覆)

編譯器可以通過 setup 上下文來判斷。模板編譯器會從 script 編譯時提取繫結資訊判斷該元件是否可用。

我認為匯入 xxx.vue 並應用高階元件的形式去包裝是非常罕見的一種情況。在這種情況下,您可以使用單獨的常規<script>標籤用以前的方式去註冊元件。

必圖(btoo 🇺🇸)

我寧願用 svelte 的 $: 而不是 svelte-ref: 這種形式。

其實也可以通過在變數名前新增一個$字首來保持訪問原始 ref 的方式。

⚠️譯者注:svelte 是國外另一款特別火的框架,這個提案的靈感就來源於這個 svelte 的寫法

約翰遜(johnsoncodehk 🇭🇰)

⚠️譯者注:這個人不是高贊(👍),而是高踩(👎),咱們看看也別光看高贊,高踩也挺有意思

$: + let / const 這種寫法怎麼樣怎麼樣? 就像這樣:

$: let foo = 1 // 這個變數代表ref
$: const bar = foo + 1 // 這個變數代表computed
$: let baz = computed(() => bar + 1)
複製程式碼

然後會被編譯成這樣:

const $foo = ref(1)
let foo = unref($foo)
const $bar = computed(() => foo + 1)
const bar = unref($bar)
const $baz = ref(computed(() => bar + 1))
let baz = unref($baz)
複製程式碼

我感覺大家好像都不想脫離 js 的語義,當然我們可以有一種完全基於 js 語義的方法,但這是我們想要的嗎:

import { noValueRef, noValueComputed } from 'vue'
let foo = noValueRef(1) // TS型別: number & { raw: Ref<number> }
const bar = noValueComputed(() => foo + 1) // TS型別: number & { raw: ComputedRef<number> }
useNum(bar.raw)
function useNum(num: Ref<number>)
複製程式碼

然後會被編譯成這樣:

import { ref, computed } from 'vue'
let foo = ref(1)
const bar = computed(() => foo.value + 1)
useNum(bar)
function useNum(num: Ref<number>)
複製程式碼

喜歡的點贊(👍),不喜歡就踩(👎)

⚠️譯者注:上面那句話是他說的不是我說的,果然亞洲都喜歡玩這一套

羅賓鮑烏(RobbinBaauw 🇳🇱)

這個 RFC 的缺點(<script setup>本身也是一個缺點,但現在變得更糟)是編寫大量相同內容的 options。經驗豐富的 Vue 開發者(例如對此RFC進行評論的這幫人)瞭解所有的 options,瞭解它們之間的關係等,但是對於 Vue 的大部分開發者而言,情況並非如此。

在 Vue2 中,只有 Options API,最終我們會得到一個類似類一樣的元件。但是如果用了這個 RFC,我們將會面臨以下選擇:

  • Options API
  • Class API (需要用外掛)
  • Composition API (vue2 和 vue3 的寫法也不一樣)
  • <script setup> 搭配ref:語法糖
  • <script setup> 不搭配語法糖

這會讓使用者群變得非常分散,讓剛入門的小白和有選擇困難症的人感覺太難了。如果只需要記住:"想要響應式就用 Options API,方便邏輯複用就用 Composition API"的話那就會簡單多了。

但是很明顯,不少人喜歡這種"神奇的"語法。如果有人再提出個什麼自定義的語法說不定以後還會想再擴充套件語法,能不能在 Vue 以外的第三方庫去實現它?如果這是 Vue 的核心語法,那麼在以後的 Vue 版本中都將需要支援它。

我認為這個$非常令人疑惑:如果您不完全瞭解$字首的前因後果,我認為這將會令人非常疑惑!也就是說,需要知道一個這玩意是個 ref 的實際值,並且還需要知道自己正在處理的是一個加了語法糖的 ref,在這種情況下需要給它加上$字首,而在其他情況下,則不需要加字首。我堅信這會給許多使用者帶來很多問題。

尤雨溪(回覆)

你說的太誇張了。

Class API 是一個官方外掛,但只是一個外掛。不是核心API的一部分,僅吸引特別偏愛 Class 的使用者。換句話說,它不是"主流"。

⚠️譯者注:不是主流那不就是非主流

vue2 和 vue3 的 Composition API 的目的是相同的,並且即使不是全部,也至少是大多數程式碼看起來相同。微小的技術差異不會將它們視為"做同一件事的兩種方式"。

<script setup>就如 RFC 中所建議的那樣,用不含語法糖編寫的 Composition API 程式碼與正常的 Composition API 使用情況 100% 相同(除了無需手動返回所有內容)。實際上,如果使用者使用的是但檔案元件的話,由於前者總是隻用寫更少的程式碼,所以我真找不到不用新<script setup>的理由。

難道你覺得這麼寫更好嗎:

export default {
  components: {...},
  setup() { ... }
}
複製程式碼

ref:是純語法糖。它不會改變 <script setup> 裡面 Composition API 的工作方式。如果你已經理解了 Composition API,那麼理解ref:語法糖不會超過10分鐘。

綜上所述-有兩個"範例": (1)Options API (2)Composition API <script setup> 和ref:語法糖不是不同的 API 或範例。它們只是 Composition API 的擴充套件,可以用更少的程式碼來表達相同的邏輯。

(引用羅賓剛才說的話):如果有人再提出個什麼自定義的語法說不定以後還會想再擴充套件語法,能不能在 Vue 以外的第三方庫去實現它?

所以,你也同意很多人都希望使用ref:語法糖,但是卻建議不要在 Vue 中支援它,而是鼓勵各種第三方庫各自實現自己的語法。這不是隻會導致更多的碎片化嗎?想想 React 生態系統中的CSS-in-JS。

(引用羅賓剛才說的話):我認為這個$非常令人疑惑:如果您不完全瞭解$字首的前因後果,我認為這將會令人非常疑惑!也就是說,需要知道一個這玩意是個 ref 的實際值,並且還需要知道自己正在處理的是一個加了語法糖的 ref,在這種情況下需要給它加上$字首,而在其他情況下,則不需要加字首。我堅信這會給許多使用者帶來很多問題。。

$foo 其實就代表 foo 就好比 foo 就代表 foo.value。真的那麼令人迷惑嗎?這會導致什麼確切的具體問題?忘記$何時新增?請注意,即使沒有語法糖,我們也忘記什麼時候該用.value,後者更可能發生。

你是在要求使用者放棄 Composition API 的好處,因為您不喜歡語法糖,因為語法糖會使Composition API 變得不太冗長。我真的認為沒有道理。

林納斯·伯格(LinusBorg 🇩🇪)

如果 <script setup> 現在直接向 <template> 模板公開頂級變數

那將如何處理中間變數:

const store = inject('my-store')

ref: stateICareAbout = computed(() => store.state.myState)

function addState(newState) {
  store.add(newState)
}
複製程式碼

即使 store 這個變數不需要公開到 <template> 上去,也會暴露給<template>模板。但這實際上是一個問題嗎?

不利的一面是: 它將使生成的程式碼變大,因為它會使設定返回的物件變大,並且我們對模板所暴露的內容失去了一些"清晰度"。

從好的方面來說,人們可能會爭辯說,明確定義暴露於模板的內容太麻煩啦,感覺 <template> 現在更像是 JSX 了。

尤雨溪(回覆)

是的,所有頂級變數都會暴露在外。從技術上講,如果將其合併,我們還可以引入另一種模板編譯模式,在該模式下,直接從 setup 中 return 一個 render 函式。這使得作用域更加直接,並且避免了 render proxy。

埃勒夫(iNerV 🇷🇺)

這太糟糕了。我不想在 Vue 中看到 svelte。(不想看到不合法的 js 語法)

總結

總體來說,持反對態度的人佔多數,和國內差不多,不過看到這麼多不同國籍的人在這裡討論還挺有意思的,但是中國🇨🇳的聲音卻非常少,好不容易看到一個還是香港🇭🇰的,大家也可以去 GitHub 上直接舌戰群儒。

當然哈,別用中文,畢竟你要讓所有國籍的人都能看懂(至少是勉強看懂,我翻譯荷蘭那小子的英文,感覺說的也不咋地,咱們英文不比他們差),想象一下這些人如果都用自己的母語發表觀點的話咱們還怎麼讀懂。強行用中文的話同胞們看著是舒服了,但是會導致咱們在國際上的名聲進一步下滑。

大家在去 GitHub 討論的時候還是要儘量維持一下咱們國家在國際上的形象哈!

該文章首發於公眾號:《前端學不動》

往期精彩文章