如何评价 Vue 的 ref 语法糖提案?

这份提案引入了 <script setup> 来自动生成组件 setup 的 boilerplate 代码,并在其中用形如 ref: count = …
关注者
470
被浏览
347,413

80 个回答

只要类型支持的好,多写个.value也不介意,同理 ref label类型支持的好的话,也没那么多心智负担

注意注意,这只是一个提案!提案不一定会落地,重点也不是说服大家这一定是个好主意,而是在于邀请大家一起探讨设计的取舍和边界。不喜欢的也请嘴下积德。

RFC 本身加上原帖里的回复我已经写了大概快上万字的英语了,但看起来知乎很多回答应该是没看完就急着发表意见了,所以还是用中文针对一些常见的疑问和误解解释一下。

看上去不像 JavaScript

ref: count = 1 使用的是标签语法,在 syntax 层面是合法的 JavaScript,而且在非严格模式下是可以正常执行的,甚至语义也是声明了一个名为 count 的全局变量。同时这也是合法的 TypeScript 语法,不会和类型声明混淆(类型声明必然需要 let 和 const)

当然这里确实只是语法层面的合法,实际上等于是给 ref: 这个标签赋予了一个不同的语义。标签语法本身是一个极少被使用的功能,实际使用也都是用于标记循环声明(用在 for/while 前面),像例子中 ref: count = 1 这样的用法,其原始语义是毫无用处的,这也是为什么我们认为牺牲这个原始语义来获得响应式的变量声明是一个值得的交换。

为什么用标签语法而不是直接发明新语法

使用标签语法确实是受到了 Svelte 的启发。根本原因在于和 JS 保持 syntax 层面的完全兼容能够尽可能保证现有的 JS 工具生态对接。标签语法能够正确地被 Babel,TS parser/transformer(如 esbuild/swc),Prettier,ESLint 以及任何 IDE 的 JavaScript 语法高亮所直接支持,只有在涉及语义的情况下,如类型推导和 ESLint 变量相关规则才需要针对性的兼容。如果用一个全新的非标准语法,就意味着需要在 parser 层面对上述所有工具进行修改,基本不可行。

类型推导的问题 RFC 里也有专门讨论,有兴趣的可以看原文,这里不赘述。

感觉心智负担变重了

虽然底层是编译到 ref() 的语法糖,但其实对于新人来说根本不需要知道 ref() 的存在就可以使用,因为在不需要获取底层 ref 对象的场景下,通过 ref: 声明的变量心智模型和用 let 声明的变量的心智模型完全一致。你就把 ref: 当成一个响应式的 let 就行了。这个模型已经足够实现大部分入门级别的功能,只有到进阶之后开始学习逻辑抽取复用时,才需要知道 ref() 的概念。

对于已经学习了 Composition API 的用户来说会觉得 “又多了一个概念”,同时由于 RFC 事无巨细地讨论了编译的规则,会产生一种 “心智负担增加了” 的错觉。其实我很久以前用 CoffeeScript,Babel,或是刚开始用 TS 的时候,也有这样的感觉,因为我喜欢用之前先看看这东西编译出来是个什么样子。结果就是看过了这个之后用着上层语法,脑子里忍不住去把它转换成底层语法。但这本质上是我们的大脑在习惯了底层思维方式之后的一种惯性。这种惯性在使用新语法一段时间之后很快就消失了,我们的大脑适应能力还是很强的。如果你开始就不 care 编译出来是个什么结果,就更不会有这个问题(你用 nullish coalescing 或者 decorator 的时候会去想着 babel/TS 编译出来是个什么结果么?)

对于从零开始的用户来说,如上所述 ref: 就是一个能触发响应的 let 而已,学习成本是很低的。具体可以看一个对比:

Options API

<script>
import Foo from './Foo.vue'

export default {
  components: {
    Foo
  },
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

<template>
  <Foo @click="increment">{{ count }}</Foo>
</template>

Composition API (raw)

<script>
import Foo from './Foo.vue'

export default {
  components: {
    Foo
  },
  setup() {
    const count = ref(0)
    const increment = () => { count.value++ }
    return {
      count,
      increment
    }
  }
}
</script>

<template>
  <Foo @click="increment">{{ count }}</Foo>
</template>

Composition API (基于当前提案)

<script setup>
import Foo from './Foo.vue'

ref: count = 0
const increment = () => { count++ }
</script>

<template>
  <Foo @click="increment">{{ count }}</Foo>
</template>

亲自用一下感觉会更直观:antfu/vite-starter-ref-sugar

也可以看一下一个已经实际使用该语法的用户的代码和他的使用体验:github.com/vuejs/rfcs/p

其他一些疑问

review 时没办法一眼看出来这是普通变量还是ref变量。

Vetur 可以给 ref: 声明的变量加上不同的高亮。

在 SFC里用 ref: 语法糖,日后想把逻辑抽到 .ts/.js 中时,又要改成 ref() 的形式。这两种风格的不一致也影响了重构的效率。

由于编译转换规则非常简单直接,Vetur 可以提供这样的工作流:选择代码块 -> 右键 "compile ref sugar" -> 剪切 -> 黏贴。可以提供双向转换,完全机械操作,几乎没有脑力损耗。需要一提的是 Svelte 的响应编译模型无法解决这个问题,因为 Svelte 编译出来的代码不是给人维护的代码,且和组件上下文强绑定,所以没法挪到组件外部。这也顺便回答了 “既然都要语法糖了为什么不像 Svelte 那样走完全编译路线“。

这里不否认组件内外风格的切换是会有额外的心智成本的,但这是否能够接受可能会因人而异。因此,ref:语法糖完全是可选的,可以通过编译选项强制关闭。不喜欢的话依然可以在 <script setup>里使用 raw Composition API。

<script setup>now directly exposes top level bindings to template.
这使得作用域不清晰, 你不知道这个变量会不会在模板里使用

不少人表达了同样的顾虑,但有意思的是如果我们认真思考以下 “知道这个变量会不会在模板里使用“ 真的有什么实际意义吗?如果你的模版没有用到它,那么它的存在也不会产生任何的影响。另外 <script setup> 中的变量只暴露给同文件中的模版,默认不暴露给组件外部(比如父组件通过模版 ref 拿到子组件实例)。这就跟在闭包中返回一个函数一样:返回的函数能够使用所有声明在闭包中的变量,但它不一定会用到所有的变量,同时这些变量不会被闭包外的代码获取到。这样的作用域非常清晰且符合 JS 直觉。换言之<script setup> 的作用域你可以理解为在 setup() 闭包中返回 render function:

function setup() {
  let a = 1
  let b = 2

  return () => {
    // 这里可以取到 a 和 b,但可能只用到 a
    console.log(a)
  }
}
语法糖太多,同一个功能好几种写法

整体上,Vue 现在处于一个从 Options API 向 Composition API 过渡的阶段,这是一个不得已为之的情况,因为我们不可能直接废弃掉 Options API。如果你已经在使用 Composition API,那么这个提案中的语法(如果落地的话)将大概率会成为默认推荐的 SFC 书写方式。
对于刚刚上手的新人而言,由于目前的文档还是以 Options API 为主,而这个新提案还处在没有文档的讨论阶段,短期内确实可能会导致一定程度的困惑,但从长线来说最终会把文档迁移到直接用这样的语法上手的状态。


这个 RFC 提案在发表之前我就知道会引起很多争议,大部分的反应其实都在意料之中,很多提出的所谓问题在 GitHub 上已有的讨论甚至 RFC 原文中都已经讨论解释过。如果真的想要对这个提案的具体细节发表看法,还是建议先完整看完 RFC 全文和 GitHub 上的讨论。

其实第一反应 “不能接受” 我是能理解的,不过新技术一开始觉得不能接受最后真香的例子多得很,当初 TypeScript,Hooks,Composition API,甚至是 React/JSX 出来的时候不能接受的人也很多。有兴趣的可以看看 Pete Hunt 的知名演讲 Rethinking Best Practices