Vue3中的setup有什么优缺点?
诱因
首先 setup 函数有冗长返回语句的问题已经在
至于我们为什么要用 setup,我想既然同学们要了解 Vue 3,应该对这个大版本要做和已经的事情有一个更全面的认知:
Vue 3 的任务是:补短板 + 提上限
短板何在?
在今天我们看到的大部分较大的国内互联网公司所提供的 Web 产品中,使用的构建框架都是 React,我自己是字节,而我惊奇的发现 VueConf 2021 上这么多位分享的大佬竟是出自我们这个用 React 更多的公司,有同学玩笑戏称“ 字节把懂 Vue 的都抓去写 React 了 ”。
而我之前在腾讯微信支付数据中心实习过一小段时间,那里有一些内部平台系统为了开发快速简单,选择了容易上手的 Vue,但普遍项目量级都还不算特别大。
一个令人好奇的,与此相关的问题出现了:为什么大公司不敢用 Vue ?
据我自己的体验来看,可以分为以下两点,这应该就是 Vue 过去的短板:
- 2.x 版本对 TypeScript 的支持是硬伤,而 TypeScript 对大型项目的保障能力是被普遍认可的,看到 Vue 没法支持,在选择技术栈时很容易放弃它。
- 一旦项目体量变大,Vue 的代码会变得更难以维护,真正在实践中 Options API 虽然在组件层面上,每个内容的职责都很清晰,这是 data,那是 method,但是从跨组件的角度来看就没那么好了,因此 Vue 2 缺少一种真正更好的抽象逻辑的办法,而非将代码搬到独立的 .js 文件里来缩减 SFC 代码行数。
解决这两个问题的思路也很明确,尤大也在很多视频演讲中提到了:
- 函数是对类型最友好的,输入、输出的类型都是确定的,易推导的。若要这样做,显然是用 TypeScript 重写一遍 Vue 更好。
- 虽然我们的页面内容被划分成了一个个的组件,但是我们思考的逻辑却不应该僵死地被他们框住,不需要在每个组件中罗列他们本身的职责,而是让组件去适配、去载入我们可多处复用的逻辑。Vue 需要为开发者提供一套新的框架内 API,使得程序员们能创造出更容易复用的 “ 业务 API ”。若要这样做,显然需要一套新的函数式响应式 API,这些东西将成为我们书写业务逻辑的 “原语”
这些更新内容都在 Vue 3 中完成了,通过一些社区中新的插件对 TypeScript 集成,我们还获得了更好的 DX 体验。
总结
所以请题主莫要纠结于 setup 本身,因为把所有逻辑不加抽象、不加简化地都写在它其中,本就是不对的。setup 只是为我上面说的 组件载入逻辑 提供了一个入口,而你不应该把所有东西都摆在门口。
Options API 对一些小的,功能简单纯粹且独特的组件还是能用上的。另外如果你喜欢它,Vue 也没有删掉对其的支持。
Vue3 Ref 语法糖,告别 .value 的写法
Vue3 提了一个 Ref Sugar 的 RFC,即 ref
语法糖,目前还处理实验性的(Experimental)阶段。在 RFC 的动机(Motivation)中,Evan You 介绍到在 Composition API 引入后,一个主要未解决的问题是 refs
和 reactive
对象的使用。而到处使用 .value
可能会很麻烦,如果在没使用类型系统的情况下,也会很容易错过:
let count = ref(1)
function add() {
count.value++
}
所以,一些用户会更倾向于只使用 reactive
,这样就不用处理使用 refs
的 .value
问题。而 ref
语法糖的作用是让我们在使用 ref
创建响应式的变量时,可以直接获取和更改变量本身,而不是使用 .value
来获取和更改对应的值。简单的说,站在使用层面,我们可以告别使用 refs
时的 .value
问题:
let count = $ref(1)
function add() {
count++
}
那么,ref
语法糖目前要怎么在项目中使用?它又是怎么实现的?这是我第一眼看到这个 RFC 建立的疑问,相信这也是很多同学持有的疑问。所以,下面让我们来一一揭晓。
1 Ref 语法糖在项目中的使用
由于 ref
语法糖目前还处于实验性的(Experimental)阶段,所以在 Vue3 中不会默认支持 ref
语法糖。那么,这里我们以使用 Vite + Vue3 项目开发为例,看一下如何开启对 ref
语法糖的支持。
在使用 Vite + Vue3 项目开发时,是由 @vitejs/plugin-vue
插件来实现对 .vue
文件的代码转换(Transform)、热更新(HMR)等。所以,我们需要在 vite.config.js
中给 @vitejs/plugin-vue
插件的选项(Options)传入 refTransform: true
:
// vite.config.js
import {
defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue({
refTransform: true
})]
})
那么,这样一来 @vitejs/plugin-vue
插件内部会根据传入的选项中 refTransform
的值判断是否需要对 ref
语法糖进行特定的代码转换。由于,这里我们设置的是 true
,显然它是会对 ref
语法糖执行特定的代码转换。
接着,我们就可以在 .vue
文件中使用 ref
语法糖,这里我们看一个简单的例子:
<template>
<div>{
{
count}}</div>
<button @click="add">click me</button>
</template>
<script setup>
let count = $ref(1)
function add() {
count++
}
</script>
对应渲染到页面上:
可以看到,我们可以使用 ref
语法糖的方式创建响应式的变量,而不用思考使用的时候要加 .value
的问题。此外,ref
语法糖还支持其他的写法,个人比较推荐的是这里介绍的 $ref
的方式,有兴趣的同学可以去 RFC 上了解其他的写法。
那么,在了解完 ref
语法糖在项目中的使用后,我们算是解答了第一个疑问(怎么在项目中使用)。下面,我们来解答第二个疑问,它又是怎么实现的,也就是在源码中做了哪些处理?
2 Ref 语法糖的实现
首先,我们通过 Vue Playground 来直观地感受一下,前面使用 ref
语法糖的例子中的 <script setup>
块(Block)在编译后的结果:
import {
ref as _ref } from 'vue'
const __sfc__ = {
setup(__props) {
let count = _ref(1)
function add() {
count.value++
}
}
可以看到,虽然我们在使用 ref
语法糖的时候不需要处理 .value
,但是它经过编译后仍然是使用的 .value
。那么,这个过程肯定不难免要做很多编译相关的代码转换处理。因为,我们需要找到使用 $ref
的声明语句和变量,给前者重写为 _ref
,给后者添加 .value
。
而在前面,我们也提及 @vitejs/plugin-vue
插件会对 .vue
文件进行代码的转换,这个过程则是使用的 Vue3 提供的 @vue/compiler-sfc
包(Package),它分别提供了对 <script>
、<template>
、<style>
等块的编译相关的函数。
那么,显然这里我们需要关注的是 <script>
块编译相关的函数,这对应的是 @vue/compiler-sfc
中的 compileScript()
函数。
2.1 compileScript() 函数
compileScript()
函数定义在 vue-next
的 packages/compiler-sfc/src/compileScript.ts
文件中,它主要负责对 <script>
或 <script setup>
块内容的编译处理,它会接收 2 个参数:
sfc
包含.vue
文件的代码被解析后的内容,包含script
、scriptSetup
、source
等属性options
包含一些可选和必须的属性,例如组件对应的scopeId
会作为options.id
、前面提及的refTransform
等
compileScript()
函数的定义(伪代码):
// packages/compiler-sfc/src/compileScript.ts
export function