VUE 新语法糖魔改 JavaScript 引争议
The following article is from OSC开源社区 Author 大东BE
(给前端大全加星标,提升前端技能)
作者:OSC开源社区 公号 / 大东BE
近日,VUE 作者尤雨溪在社区意见征求稿(RFC) 上提交了一份 Ref 语法糖的提案,引发了社区的争议。
简要地说,这份提案在单文件组件(SFC)中引入了一个新的 script 标签写法,写法为 <script setup>
,这种写法会自动将所有顶级变量声明暴露给模板(template)使用。同时在 <script setup>
中引入了一个消除 ref 的 value 属性的语法糖,该语法糖在编译期间自动将语法糖转为正常代码。
例如这样的 HTML 代码:
<script setup>
// 声明一个会被编译到 ref 的变量
ref: count = 1
function inc() {
// 该变量可像普通的值一样使用,无需 .value
count++
}
// 用 $ 前缀对应原始的 ref 对象
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>
尤雨溪表示,一方面是通过自动暴露顶级变量可以减少代码的冗余度;另一方面,通过 ref:
语法可以让 ref 更高效。
自从 Composition API 诞生以来,大家就一直在讨论到底用 ref 好还是用 reactive 对象好。用 ref 会导致代码中到处都是 .value
,而且如果开发者没有使用 TypeScript,还会经常漏写 .value
,所以很多开发者不知道该怎么处理 ref。
实际上 ref 的存在是因为 JS 这门语言存在一些问题:如果不把一个值封装在一个对象里,就无法通过 JS 自带的方法把这个值变成一个响应式的数据。这意味着如果不魔改 JS 的语义,就不可能像是用普通变量那样使用 ref。此外,尤雨溪也承认自己是从 Svelte 框架得到了启发,Svelte 早前就魔改了 JS 的语义, export
let
$
的语义都被 Svelte 赋予了更强大更方便的功能。
针对这一提案,很多社区用户留言表示反对,大多数人反对的原因还是集中在这一提案对 JavaScript 魔改太严重 ,质疑为什么用标签语法而不是直接发明新语法,让用户的心智负担加重了等等。
针对这些质疑,尤雨溪本人也一一做出了回应:
关于魔改 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 层面对上述所有工具进行修改,基本不可行。
感觉心智负担变重了
虽然底层是编译到 ref() 的语法糖,但其实对于新人来说根本不需要知道 ref() 的存在就可以使用,因为在不需要获取底层 ref 对象的场景下,通过 ref: 声明的变量心智模型和用 let 声明的变量的心智模型完全一致。用户只需要把 ref: 当成一个响应式的 let 就行了。这个模型已经足够实现大部分入门级别的功能,只有到进阶之后开始学习逻辑抽取复用时,才需要知道 ref() 的概念。
对于已经学习了 Composition API 的用户来说会觉得 “又多了一个概念”,同时由于 RFC 事无巨细地讨论了编译的规则,会产生一种 “心智负担增加了” 的错觉。其实我很久以前用 CoffeeScript,Babel,或是刚开始用 TS 的时候,也有这样的感觉,因为我喜欢用之前先看看这东西编译出来是个什么样子。结果就是看过了这个之后用着上层语法,脑子里忍不住去把它转换成底层语法。但这本质上是我们的大脑在习惯了底层思维方式之后的一种惯性。这种惯性在使用新语法一段时间之后很快就消失了,我们的大脑适应能力还是很强的。如果你开始就不 care 编译出来是个什么结果,就更不会有这个问题(你用 nullish coalescing 或者 decorator 的时候会去想着 babel/TS 编译出来是个什么结果么?)
对于从零开始的用户来说,如上所述 ref: 就是一个能触发响应的 let 而已,学习成本是很低的。
尤雨溪回答原文:https://www.zhihu.com/question/429036806/answer/1564223482
最后,尤雨溪表示在这个 RFC 提案发表之前自己就知道会引起很多争议,也非常理解人们面对新技术的第一反应是“不能接受”。不过他也表示提案未必就会实装,希望大家讨论的时候保持理性,同时建议大家完整看完 RFC 全文和 GitHub 上的讨论后再提出质疑。
觉得本文对你有帮助?请分享给更多人
推荐关注「前端大全」,提升前端技能
点赞和在看就是最大的支持❤️