阅读 6987

[译]尤雨溪: Ref语法糖提案

前言

最近 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 讨论的时候还是要尽量维持一下咱们国家在国际上的形象哈!

该文章首发于公众号:《前端学不动》

往期精彩文章

文章分类
前端
文章标签