最近新入职的公司需要使用Vue3做项目,所以我这个V3的排斥者也只能选择了这个方法。
对于V3,除了一年前我稍微了解了一下,后续就没有跟进,我个人对技术追星并未有太多的兴趣。
直到我见识了这家公司项目的写法,我只能说,V3确实有那么点的意思。
正文
本文虽说是探讨ref和reactive的区别,但不会深入探讨具体的源码解析,只更倾向于如何在项目中更好的使用。
毕竟,我们不是造轮子的,我们是用轮子的。
在现在这个前端大爆炸的时代里,我们已经不缺一个好用的框架,更缺的是一个用好框架的思路。
refs与reactive的区别
不管怎么说,在正式讲使用心得之前,还是要简单说说二者的区别。
ref
响应式改变需要使用变量.value
,被其包裹的变量可以是简单类型,也可以是引用类型。
在开发中,我们常用来包裹一些特殊的响应式变量,通用形式的变量我个人不喜欢用ref,如果是引用类型的话,更倾向于是用reactive()
1 | <script setup> |
reactive
同样的代码,reactive不需要用变量.value
这种写法,可以直接引用。
1 | <script setup> |
不过,reactive有一定的局限性。
有限的值类型:它只能用于对象类型 (对象、数组和如
Map
、Set
这样的集合类型)。它不能持有如string
、number
或boolean
这样的原始类型。不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象,因为这样的话与第一个引用的响应性连接将丢失
1
2
3
4
5let state = reactive({ count: 0 })
// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 })对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接
1
2
3
4
5
6
7
8
9
10
11const state = reactive({ count: 0 })
// 当解构时,count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state,这里的响应式无效
count++
// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count)
refs与reactive的使用方式
二者都是V3提供的响应式语法糖,但在开发过程中,很多时候二者会混用,实际上,不该那么用。
个人参考了别人的案例,整理了一下规则
- 只用
ref
声明响应式变量 - 只有当第三方库的操作对象需要具备响应性时,才使用
shallowRef
- 只有当逻辑块完整时,才使用
reactive
包裹住那块的业务
用ref
声明响应式变量
虽然reactive
声明的变量也具备响应性,但是,想要将v3用的简单,不推荐混用ref与reactive。
因此,无论是对象、数组、数字、字符串、布尔值、HTML引用,都用ref
来声明
不推荐
将
ref
和reactive
混着用,这样的响应式经常会无意识在使用变量时候要不要使用.value产生心智负担,影响开发效率。1
2
3
4const tableData = reactive([]);
const obj = reactive({})
const a = ref([])
const b = ref(1)推荐
只用
ref
声明响应式变量,这样,相关的变量我们直接就用.value来改变响应式的变量即可1
2
3
4
5
6
7const arr = ref([])
const obj = ref({})
const num = ref(0)
const str = ref('')
const bool = ref(false)
const nil = ref(null)
const udf = ref()
用shallowRef
操作第三方库的对象
什么是第三方库的操作对象?指的是那些不是开发时声明出来的,而是第三方库API创建并暴露出来给你操作的对象。
比如Echart初始化之后会产生一个对象,允许你调用其
setOption
方法来更新图表,这个对象就属于第三方库的操作对象。
通常情况下,对于第三方库的操作对象,是不需要添加响应性的:
1 | let mychart = null |
但是在有些场景下,你需要让这个对象具备响应性:比如你需要以props的形式将这个操作对象传递给子组件,并且这个操作对象还可能发生变化,你希望子组件也能跟着变化。
那么这时候你应该只使用shallowRef
这个API,为这些你不知道底细的第三方库产生的对象提供响应性支持
1 | let mychart = shallowRef(null) |
- 为什么不能使用ref给这些对象提供响应性?因为
ref
和shallowRef
的区别在于,ref
会遍历整个对象,给对象的每个属性都创建响应性,无论是多深的对象,你给任何一个属性赋值,都会刷新界面。而shallowRef
相反,shallowRef
只有浅层的响应式处理,只有给其.value
赋值时,才会触发界面刷新。 - 这就导致一些问题,当第三方库的操作对象也在监听内部数据自我更新时,就会产生一种:“你更新了,我监听到了,我更新;我更新了,你监听到了,你又更新;你又更新了,我监听到了,我又又更新…”的死循环中,然后导致页面崩溃。
- 所以对于你不知道底细的对象(通常情况下也就只有第三方库会产生),直接使用
shallowRef
创建响应性。
用reactive
包裹完成的逻辑块
什么是 组合式函数,指的是那些将响应式变量封装起来的函数。官方文档传送门:组合式函数 | Vue.js (vuejs.org)
组合式API最大的优势在于函数级别的复用,这也是最不同于V2的地方,V2想要复用一块完成的逻辑块,大多用mixinjs混合,但是很多情况下并非特别好用。
我看到项目中这套配合动态表单的组合式风格,简直是惊为天人。
这样的代码逻辑太过清晰易读,简直比之前V2写的低代码还牛逼。
- 逻辑模块聚合原则:相同逻辑的代码必须写在一起
- 业务代码优先原则:在无复用的需求下,不需要将业务代码高度封装成组合式函数。
- 核心模块保留原则:一个vue组件改动最频繁且无复用的逻辑代码,属于核心代码,应该保留在vue组件里而不是ts文件里。
1 | /**@table - 用reactive包装,将变量和方法都封在这一个逻辑块中,这块的算是最核心的代码*/ |
如果你是选项式的粉丝,你甚至可以这样玩:
1 | /**@module 模块1*/ |
当然,后边这个写法有点玩票的性质,前者的写法亲自体验之后,惊为天人,设计这套写法的人,抽的太干净了,稍微设计一下,几乎就是低代码的写法。
核心逻辑配合动态表单,所有的业务代码简直是快速生成的利器。
选项式与组合式
以下这段话说的比较虚,并不像上边那么务实,算是这么长时间使用之后,个人的一点心得。
选项式写法,即传统V2写法,按照已经框好的代码块,变量和方法都被放在了规定的位置。
组合式写法,相对前者更为自由,没有V2规定好的变量块和逻辑块,更偏向于传统JS式的写法,开发者只要将对应的逻辑块组到一块就可以,这样更便于代码阅读。
这两种写法均被Vue3支持,可根据自己习惯而定。
根据之前项目踩坑的经验来看,如果团队的代码能力不足,不建议使用组合式写法。
不规范且不同习惯的组合式代码放在一起,在后期维护的时候会非常让人崩溃,vue2的写法虽然死板一些,但是某种意义上也增加了规范,至少有一定的可读性。
我个人认为,组合式的写法想要用的过瘾,需要以下几点
- 团队有一定的前端开发基础,大体的开发人员水准不会太差,这样能保证组合式代码写出来的可读性不会太差。
- 有一份不错的动态组件库,直接用半个配置化写法整合所有的代码,这样可以大规模减少非业务的代码,减少代码阅读的心智负担
- 前端有一份通用的表单处理规范,这样阅读他人的代码逻辑时候,心智负担不会太高,再写好一个完美的个例之后,便于快速复用,其他人能很快读懂。
总结
- 为了避免认知混乱,基本上都使用
ref
进行声明响应式变量 - 为了避免页面卡死,对于那些你不知道层级结构的数据,使用
shallowRef
为它创建响应性 reactive
推荐用来包裹一些完整的逻辑块,在reactive
中写方法引用变量会非常方便,而且也便于阅读
结语
vue3的升级,果然不仅仅是一次写法上的升级,动态组件库配合组合式写法,简直超神。
一篇业务模块的文件中,几乎没有多少业务代码,而且相对V2的难于抽离,V3的可以直接将弹窗封在子组件中,这种感觉真不错。