⚠️本文为掘金社区首发签约文章,未获授权禁止转载
前言
其实之前Vue3做过好多次语法糖的提案,最经典的莫过于<script setup>提案。但一开始这个提案夹杂着ref语法糖,所以很多批评的声音接踵而来:什么Vue又开始创造新概念啦、不忠于JavsScript啦、不如叫<script lang="vue-script">之类的…
但尤雨溪发现反对的意见大多数是对ref语法糖不满,于是继续细分,把<script setup>和ref语法糖分成了两个不同的提案,如果不太清楚我说的到底是什么东西的话,可以点进这两篇文章看一看:《[译]尤雨溪: Ref语法糖提案》、《Vue 3.0.3 : 新增CSS变量注入以及最新的Ref提案》
最近我看到<script setup>这个提案终于定稿了,已经进入Vue的标准里面去了,我们在用新版Vue的时候是默认支持这种写法的。不过由于ref这个提案反对意见太多,尤大怕如果不顾大家的反对意见坚决推进的话,可能会失去大家的信任从而流失一批用户、顺便再给自己多招点黑…
于是ref这个提案就被放弃掉了。正当我以为终于不用再搞那些花里胡哨的玩意之后,新版的ref语法糖提案又来了… 原来尤大解决ref的.value属性这个决心一直都没有改变,你们不同意原来的写法?那好,换个语法再来一遍!
为什么老想做这个 ref 语法糖?
自从引入 Composition API 以来,一个主要未解决的问题是 ref 对象的使用。.value在任何地方使用都可能很麻烦,如果不使用 TS 的话,很容易就会忘记写这个.value属性,就像这样:
import { ref } from 'vue'
let loading = ref(true)
if (loading) {
    // 此处省略若干代码
    loading = false
}
复制代码但实际上我们要写成这样才会正确运行:
if (loading.value) {
    // 此处省略若干代码
    loading.value = false
}
复制代码这就很烦,所以一些用户特别倾向于只用reactive()这个函数,这样他们就不必面对ref的.value属性了,就像这样:
import { reactive } from 'vue'
const state = reactive({
    loading: true
})
if (state.loading) {
    // 此处省略若干代码
    state.loading = false
}
复制代码但其实这些写法在尤雨溪的眼里都不是最好的解决方案,于是他参考了Svelte的写法,用了几乎快被废弃掉的label语法:
ref: loading = true
if (loading) {
    // 此处省略若干代码
    loading = false
}
复制代码这个语法为何遭到大家的强烈反对呢?因为我们声明一个变量通常会用let、const以及var关键字对吧,但这个压根儿就没用到任何声明的关键字,取而代之的是不伦不类的ref:。这个语法并不是尤雨溪自创的啊,它是JS里的label语法,但几乎没人用,可能有一部分人听都没听过,它主要是在多重嵌套的循环中配合break及continue使用的,就像这样:
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
复制代码看不懂没关系啊,也没必要弄懂这种语法,因为它不够直观,用处也不是很大,所以几乎没什么人用它!不过既然没什么人在用,同时它还是JS的合法语法,那用它来告诉编译器这里是声明了一个ref变量岂不是很完美?
那么大家为何会如此反对呢?就是因为label语法压根儿就不是这么用的,人家原本是为了和break、continue配合使用的,虽然在别的地方用也不算是语法错误,但你这么做明显是修改了JS原本的语意!
那尤大新提的这个ref语法糖长什么样呢,我们来看一下:
<script setup>
let loading = $ref(true)
if (loading) {
    // 此处省略若干代码
    loading = false
}
</script>
复制代码尤大心想:你们不是嫌我之前用了不规范的语法么?那我这回这么写应该没问题了吧!想想之前我们定义一个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>
<!-- 现在 -->
<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而产生的,比方说我们在别的地方写了一个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>
复制代码这岂不是又要出现尤大最不想看到的.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所创建出来的其实是一个响应式对象,而不是一个基本数据类型,但语法糖会让我们在使用的过程中像是在用基本数据类型那样可以改来改去,但有时我们想看看这个对象长什么样,那么我们就需要用到$raw了:
<script setup>
const loading = $ref(true)
console.log(loading) // 其实打印的不是 loading 这个对象 而是它里面的值 相当于 loading.value
console.log($raw(loading)) // 这回打印的就是 loading 这个对象了
</script>
复制代码嵌套在函数作用域内的语法糖用法(尚未实现)
从技术上来讲,$ref可以在任何地方被let声明使用,包括嵌套函数范围:
function useMouse() {
  let x = $ref(0)
  let y = $ref(0)
  function update(e) {
    x = e.pageX
    y = e.pageY
  }
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))
  return $raw({
    x,
    y
  })
}
复制代码上面的代码将会被编译成这个样子:
import { ref } from 'vue'
function useMouse() {
  let x = ref(0)
  let y = ref(0)
  function update(e) {
    x.value = e.pageX
    y.value = e.pageY
  }
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))
  return {
    x,
    y
  }
}
复制代码不过目前尚不支持这种写法,仅支持不在函数或者其他块级作用域中的ref语法糖。
尤大还不确定是否要做的功能
这种语法糖是否要在单文件组件的外部进行支持
这种语法糖本质上是可以通过babel等编译工具来转换成任何合法的JS或TS代码的,但新语法目前仅支持写在<script setup>的单文件组件里,这是因为:
- 尽管是语法上有效的JS或TS语法,但它毕竟不是标准JS语义。JS里并没有$ref、$computed这种全局变量。在单文件组件中的<script>加上一个setup属性就是用来表示里面的代码将会被预处理一些特殊行为。
- 因为它被实现为@vue/compiler-sfc这个模块的其中一部分,所以它允许现有的Vue用户在开始使用新语法时不需要任何额外的babel等配置。
- <script setup>的编译过程已经实现全- AST解析,所以- ref语法糖的变换可以重复使用相同的- AST,并避免产生额外的解析开销。
- 新语法的转换还会被编译器进行智能绑定。
如果新语法仅限于单文件组件
当我们不在单文件组件内写代码时会产生一定的心智负担。先前的研究表明,这种心理成本可能实际上减少了没有语法糖时的使用效率。
不同的语法也会产生摩擦,比方说我们想提取或跨组件重用逻辑时(就是我们俗称的hooks)。
不过幸运的是,由于变换规则相对而言比较简单,用语法糖编写的代码可以通过IDE插件来自动转换成没有语法糖的样子。
新语法如果支持所有文件
- 解析成本:我们已经解析<script setup>里面的语法了,所以新的ref语法糖并不会明显增加额外的解析成本。然而,如果应用到所有的JS或TS文件中去的话,将会显著增加额外的解析成本。
- 这种新语法并不是标准的JavaScript语义,JS里并没有$ref、$raw这种全局变量,让这种语法生效在Vue的特定环境之外可能是个坏主意。
如何开启新语法?
这种语法是随着Vue 3.2一同发布的,所以我们的Vue版本至少要大于等于Vue 3.2.0-beta.1。由于该语法是实验性的,默认是不启用的,我们需要自行配置:
在 vue-cli 脚手架中
我们需要在根目录下新建一个vue.config.js,然后在里面写:
module.exports = {
  chainWebpack: config => {
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap(options => {
        return {
          ...options,
          refSugar: true
        }
      })
  }
}
复制代码在 Vite 中
我们需要在根目录下新建一个vite.config.js,然后在里面写:
import vue from '@vitejs/plugin-vue'
export default {
  plugins: [
    vue({
      script: {
        refSugar: true
      }
    })
  ]
}
复制代码在自己搭建的 webpack 脚手架中
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /.vue$/,
        loader: 'vue-loader',
        options: {
          refSugar: true
        }
      }
    ]
  }
}
复制代码注意事项
首先这个新语法还是实验性质的,并未进入标准,尽量不要在主要项目中开启,因为实验性语法不一定就会进入标准。第一波ref语法糖提案被毙掉之后,我看到有人跑到GitHub上大加吐槽:

翻译:
我注意到 3.2 的测试版已经取消了第一波
ref语法糖的支持。我非常失望。因为我已经使用ref语法糖半年多了,据我所知它是vue3的一部分。
与其他人不同,我认为理解起来或学习起来并不难。
vue3已经出来快一年了,ref语法糖都已经9个月了。我都已经在我的团队中推进了ref语法糖的使用,它运行良好,以至于我们现在专门使用Composition API来进行开发。语法糖带来了很多好处,因为.value真的很无聊,这是与vue2的Options API的最大区别,使用语法糖可以不用写.value就具备响应式的能力和可组合性的魔力。
但是对于我和我的团队来说,这种变化非常糟糕,我们已经广泛使用了
ref语法糖。我不知道我是不是少数,但我都已经用了半年多了,因为它得到了非常好的IDE支持(感谢@johnsoncodehk ),而且在用的时候也没发现任何的bug,无论是对对象结构还是对原始值的访问都很棒。这对我的开发体验来说是一个很大的改进。
我看了一下新的语法糖,和原来的没什么区别,不还是需要编译器做魔术嘛!因为没有了
label语法导致它看起来更像原生js,但其实根本就不是。访问原始值和对象结构也变得更加乏味。添加了很多新的API:$ref,$computed,$fromRefs,$raw, 不知道以后还会不会有$shallowRef, 或者$watch?
也不知道别人会不会接受这个新语法糖提案,但是至少是伤害了原本支持和使用第一波
ref语法糖的人。由于 3.1.4 现在可以通过选项控制语法糖是否生效,我希望至少能够通过配置保留住第一波语法糖的写法。
尤雨溪在最后说到:

如前所述,本提案中使用的标签语法存在各种缺陷——特别是与标准
JS行为的语义不匹配,我们正在放弃这个提议。
再次声明一遍:请记住,标记为实验性的功能是用于评估和收集反馈意见的。它们可能随时更改或中断。除非功能的相应 RFC 已合并,否则无法保证 API 的稳定性。
@vue/compiler-sfc使用实验性功能时的警告应该已经很清楚了。通过选择实验性功能,您承认您愿意在功能更改或删除时重构您的代码。
#369提出了一个新版本的 ref 语法糖,它不依赖于挪用
label语法,也不需要专门的工具支持。它目前在3.2.0-beta中发布,并取代了本提案的实现。同样,这也是实验性的,因此上述所有内容也适用于新提案。
所以说尽可能不要在主要项目中使用它,我们可以没事写个 demo 试验一下,或者在自己的个人项目中使用,不然的话很可能就会像上面那位老哥吐槽的那样了…
其他人也觉得谁让你那么用了,既然用了就要承担风险:

你没有考虑到API 是作为实验性质引入的,以便能够根据用户反馈对其进行调整(很多人不喜欢 label 语法)。它使用户能够试验
API,在某些情况下,这对于API的体验感至关重要。当你使用实验性功能时,你将接受如果后续版本不兼容的话,你会对原来的代码进行重构甚至不得不将其删除的风险。在API稳定并合并到RFC之前,它也不是Vue 的一部分
不过话虽如此,你应该试试新版的
ref语法糖,然后再来提供反馈。因为说不定你可能更喜欢新版的语法糖而不是现有版本。
也有人支持吐槽的那位老哥:

新一波语法糖提案似乎仍旧令人费解,但这是我们在不改变 JS 原始语法的情况下所能做的最好的事情了(因为有些人总是介意这一点)我同意同时保留新旧两种语法糖。
个人观点
当然这种新语法肯定是有人喜欢有人讨厌的,我个人是比较反感这个新语法的,如果屏幕前的你喜欢这个新语法的话,那么请跳过我对这段对新语法的吐槽,以免因观点不合产生激烈互喷等情况。
首先我认为最大的弊端就是尤雨溪提出来的:这种语法糖是否要在单文件组件的外部进行支持?
如果仅在单文件组件里支持,我们在外头写hooks的时候还是要写.value属性,一会需要写一会不用写的这样不一致的写法很容易写错(虽然有工具提示可以降低错误)。但还是很烦,而且这边用着ref函数,到了另一边又变成了$ref…
如果在所有文件都支持的情况下吧,又不得不用到babel等工具进行转换,对性能又是个负担。而且有一个很难受的点就是我们还有customRef这种比较高级的API,引用官网上的一个案例:
<template>
    <input v-model="text" />
</template>
<script>
function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}
export default {
  setup() {
    return {
      text: useDebouncedRef('hello')
    }
  }
}
</script>
复制代码这种岂不是又要写.value属性?那在单文件组件里就会出现这个变量需要写.value,那个变量又不需要写的状况,很容易把人搞的头大。虽说以后对customRef这种API可能会单独再出一个$customRef语法糖,但我觉得就算写了个.value属性也没啥吧?至于就跟它较上劲了么… 虽说有时候写多了确实会稍微有点烦,但至少还是很容易理解的嘛:用.value属性触发了Proxy的getter或setter从而引发依赖收集或更新视图等操作。
还有一些其他的API如:provide、inject 等,目前的语法糖并未对它们进行兼容,所以还是会出现一会需要.value一会又不需要的情况。
还有一个最重要的点就是:一个框架的写法老是变来变去的很不利于推广,想想看Vue3.0和Vue 3.2之间有多大的差异,这次开了个坏头的话,以后就更加助长了尤大魔改编译的风气。当然他也确实是为了我们好,改的这些东西也是为了我们写起来更加的方便,有的改的也确实是不错,比如:《Vue超好玩的新特性:在CSS中引入JS变量》
还有现在已经定了稿的<script setup>语法糖,以前我们引入一个组件老需要再注册一遍:
import Xxx from 'Xxx.vue'
export default {
    components: {
        Xxx
    }
}
复制代码写多了这样的代码确实有点烦,现在我们只需要引进来就行,不用注册,但这样本质上并没有改变语意,反而新的语法糖明显改变了语意:
let loading = $ref(true)
复制代码按理说loading应该是个Proxy代理对象,但是它现在却变成了一个布尔类型的值,而且还多出来个莫名其妙的$ref函数。
当然你可能会说:你不喜欢不用不就得了?话这么说没错,但是你不用不代表别人也不用,有的人用有的人不用,这样的话在语法层面就已经产生了割裂。我们终究是要看别人代码的,有时候是接手一个遗留下来的项目,有时是在GitHub上看看别人的项目,在有的人用有的人不用的情况下就很难受。
大家怎么认为呢?可以在评论区留个言看看是喜欢这种语法糖的人多还是反对它的人多。
结语
我们把新语法糖的提案地址放在这里:github.com/vuejs/rfcs/…,希望大家可以积极参与并进去评论,但一定要注意的一点是:要用英文!
可能有人会说:都是中国人用什么英文?虽说用英文尤大可以看得懂,但评论区不全是中国人,Vue还是有相当一批外国粉丝的,而且也不全是美国人,那些不是英国人美国人的开发者,他们如果也只图自己痛快而说自己国家的母语的话,想必我们就没有办法进行沟通了,同时这也会进一步拉近国人在海外的形象:别人都用英文,就你们中国人用自己的语言,不遵守规则。
那可能有人英文水平真的很差,我们可以这样嘛:找到百度翻译,输入中文后翻译成英文,然后再把英文复制过去。虽然这样做翻译的可能不完全准确,但最起码能达到勉强看懂的地步。同时还有一个技巧就是把翻译成英文的句子再翻译回中文,看看有哪些地方的语意发生了明显的变化,我们再针对那个地方重新自己写一遍。
往期精彩文章
- 《尤雨溪国外教程:亲手带你写个简易版的Vue!》
- 《产品经理:能不能让这串数字滚动起来?》
- 《产品经理:鸿蒙那个开场动画挺帅的 给咱们页面也整一个呗》
- 《不依赖任何库打造属于自己的可视化数据地图》
- 《[译]尤雨溪:Vue3将不会支持IE11 精力会投入到Vue2.7》
- 《Vue超好玩的新特性:在CSS中引入JS变量》
- 《什么?仅靠H5标签就能实现收拉效果?》
- 《整治GitHub不文明现象!微软推出评论区!》
- 《Vue 3.0.3 : 新增CSS变量传递以及最新的Ref提案》
- 《双11小黑盒很炫酷?咱们用CSS变量来改进一下!》
- 《千万别小瞧九宫格 一道题就能让候选人原形毕露!》
- 《移动端布局面试题 全面考察你的CSS功底(居中篇)》
- 《将原型对象设置成Proxy后的一系列迷惑行为》
- 《Vue超好玩的新特性:DOM传送门》
- 《在Vue项目中使用React超火的CSS-in-JS库: styled-components》
- 《终于轮到Vue来带给React灵感了?》
- 《Vue3在IOS下的一个小坑》
- 《新版vue-router的hooks用法》
- 《[译]尤雨溪:Vue3的设计过程》























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)
