Vue官方为什么建议使用 ref() 作为声明响应式状态的主要 API?

247 阅读5分钟

前言

在Vue 3.X的文档中,在一个不起眼的地方,有这么一句话:

image.png

由于这些限制,我们建议使用 ref() 作为声明响应式状态的主要 API。

尤大在采访的时候也是希望如此

image.png

视频很短,概括起来有以下几点:

  1. 官方推荐使用ref
  2. reactive和this用法接近,vue2转过来的程序员更容易接受
  3. reactive会有过度传递的问题
  4. reactive在使用中有时候还需要使用toRef去取值,有时候需要computed去拼数据。相比较而言,ref可以直接进行操作
  5. ref和reactvie如何使用,没有对错之分,只要遵循规范就好

视频在线观看地址 - YouTube:需要翻墙

ref VS reactive

ref和reactive的不同点

ref函数和reactive函数都是用来生命响应式状态的API。但还是有点差异的,主要不同点如下:

  1. 定义数据类型不同
    • ref一般用来定义基本数据类型
    • reactive只能用来定义引用数据类型 -(对象 + 数组)

ref也可以定义引用数据类型,内部也是通过reactive来实现的。

  1. 使用方式不同

    • 操作ref的数据需要使用.value
    • 直接操作reactive的数据,无需.value
  2. 丢失响应性

    • reactive重新分配一个新对象会失去响应; 将对象传入传入函数时,会失去响应
    • ref重新分配一个新对象会不会失去响应;将对象传入传入函数时,不会失去响应

源码分析

class RefImpl<T> {
  // ....
  get value() {
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    const useDirectValue =
      this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
    newVal = useDirectValue ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = useDirectValue ? newVal : toReactive(newVal)
      triggerRefValue(this, newVal)
    }
  }
  

通过源码我们可以看到: ref就是reactive的包了一层壳而已

如果值是基本数据类型,或者是浅层(shallow)或只读(readonly),则直接赋值,不需要进行响应式处理

如果值是引用数据类型,则使用 toReactive 转换为响应式

注意:ref和reactive都是基于Proxy实现的。很多人说ref定义基础数据类型使用的是Object.defineProperty这是错误的。

牢记:ref会创建一个包含 .value 属性的对象,这个对象的响应式功能是依赖于 Proxy

官方文档 - Vue 中的响应性是如何工作的

reactive的局限性

官方文档进行了详细的说明,这里不做过多阐述,照搬官方的文档和案例

  1. 有限的值类型:reactive只能声明对象和数组

  2. 不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用

let state = reactive({ count: 0 })

// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 })

解决办法:

state = Object.assign(state , {count:1})

3. 对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接

const state = reactive({ count: 0 })

// 当解构时,count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state
count++

// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count)

reactive() 的局限性

ref能代替reactive吗?

先说答案:

不谈编码的优雅,只谈功能实现:reactive能做到的事情,ref都能做到,毕竟ref也是调用的reactive的方法

但是在项目中,很多人都不会完全舍弃reactive

  1. .value真的很烦
  2. 表单操作,使用reactive很直观,也非常方便
  3. 对于vue2转过来的程序员来说,无缝衔接

ref一把梭?

看到这,是不是更迷茫了。心里在低估,我用reactive到底对不对呀?以后的项目是不是ref一把梭到底呀?

其实不要迷茫,回归正题:ref和reactvie如何使用,没有对错之分,只要遵循规范就好。按照一种风格编码下来,遵循这种规范就好

既然是规范,首先要有制定规范的人,即有人去拍板。规范制定了,剩下的人去遵守就好了

如何制定这个规范呢?

对于小团队,尤其是单人负责一个项目。我建议用reactive去声明复杂的对象,尤其是表单数据。在computed中去做一些数据格式处理及异常处理等等。

前提是对reactive丢失响应式有深刻的理解

对于多人协作,尤其是赶工期,要求快速迭代,快速上线的那种公司。我建议还是老老实实用ref比较好。多人协作,很容易让reactive丢失响应式,产生莫名其妙的bug,排查问题,耽误时间。更有甚者,出了问题不去定位,然后toRefs, shallowRef, triggerRef满天飞,把代码整得一团糟。就像打补丁一样,补丁摞补丁,最终失去了reactive本身的意义

.value小尾巴

很多人很烦.value的小尾巴,认为每一次都要.value一下,其实很多VS Code可以帮我们解决这个问题,自动填充

比如: vue - official插件

用着确实方便,但不要忘记.value的意义是提醒我们这是一个响应式数据,至于是否启动,看各位自己的意愿了

总结

程序是写给人读的,只是偶尔让计算机执行一下。就执行而言,计算机只关心对错。因此无论使用什么方式,机器只是执行一个结果,不在意过程

因此,无论使用ref一把梭,还是正确且优雅的使用reactive,对于结果而言就是正确的。但如何平衡和取舍,需要我们自己把握,需要我们在实战中不断进步

最后再次提醒,如果不能充分了解reactive,就老老实实的使用ref一把梭,就代码的整体风格而言,更加优雅和可读。

参考资料

  1. 我们建议使用 ref() 作为声明响应式状态的主要 API
  2. 空档的交流,尤雨溪浅谈ref跟reactive的看法
  3. Vue 中的响应性是如何工作的
  4. reactive() 的局限性
  5. Vue3为什么推荐使用ref而不是reactive