Code前端首页关于Code前端联系我们

Vue3官方文档明明用reactive,早期提到的observable去哪了?它俩到底有没有区别?

terry 56分钟前 阅读数 15 #Vue
文章标签 reactiveobservable

这个问题最近在前端技术群里刷到好多次——要么是刚学Vue3的人翻到旧教程卡壳,要么是维护老Vue3RC版项目的伙伴疑惑为啥换了名,今天咱们就好好掰扯清楚,顺便给大家理理日常用这俩(或者说现在只用这一个)的正确姿势。

从诞生到消失:Vue3 observable的前世今生

Vue3在2019年10月到2020年7月这段RC和Beta阶段,确实把用于创建响应式对象的核心API命名为observable,当时的设计思路和Vue2的Vue.observable一致——都是做全局/非组件的响应式数据管理,但Vue3用了Proxy,性能和能力都强太多了。

那为啥正式版Vue3(3.0.0-rc.14之后到现在的所有稳定版)直接把observable换成reactive了?主要是两个原因:一是和当时官方在推的Composition API里“reactivity(响应式)”核心概念对齐,名字更直白,新手一看就知道是搞响应式对象的;二是避免和RxJS里最常用的observable概念混淆——毕竟前端开发里,很多时候用observable默认指的是RxJS的可观察对象,Vue3官方不想给用户添乱。

不过这里要划个小重点:虽然文档和推荐写法里没了observable,但Vue3核心包里还留着它的兼容代码!不信你可以在Vue3项目里直接import { observable } from 'vue',用起来和reactive一模一样,但从长远来看,大家最好还是用reactive,兼容代码说不定哪天就会被彻底砍掉(不过目前3.x系列官方没说要删,4.x的话就得等消息了)。

reactive和(Vue3里的)observable的本质:都是Proxy封装的壳

刚才说了,它俩兼容代码都留着,用起来一模一样,那本质肯定没啥区别,咱们可以去扒一扒Vue3的早期RC源码和现在的3.4源码对比下——早期RC版里,reactive直接就是调用了observable函数,现在的3.4源码里,observable函数直接返回了reactive的调用结果,相当于只是换了个壳子。

那咱们再说说这个“壳子”里装的核心东西:Proxy,这是Vue3和Vue2响应式原理最大的不同,Vue2的Vue.observable用的是Object.defineProperty,只能监听对象已有的属性读写,新增属性、删除属性、数组下标直接赋值、数组长度修改这些操作都得额外处理,比如用Vue.set、Vue.delete,不然界面不会更新。

Vue3的reactive(也就是observable)用了ES6的Proxy,直接代理整个对象,不管是已有属性、新增属性、删除属性,还是数组的各种操作,都能监听到,完全不需要额外的API辅助,开发体验提升了不止一点点,不过这里要注意几个Proxy的局限性: 第一,它只能代理对象、数组、Map、Set这些引用类型,不能代理基本数据类型; 第二,如果代理的是嵌套很深的对象,Vue3会采用“懒代理”的方式——只有当你访问到嵌套对象的时候,才会把嵌套对象也变成响应式的; 第三,它不能代理某些特殊的原生对象,比如Date、RegExp这些,如果你要让这些对象的变化触发界面更新,得换个方式处理,比如用ref包裹或者手动监听。

那日常开发里,我们应该怎么用reactive?

既然observable只是兼容写法,那咱们日常开发就只用reactive就行啦,接下来给大家分享几个实用的小技巧,避免踩坑。

什么时候用reactive,什么时候用ref?

很多初学者刚学Vue3的时候,会分不清reactive和ref的使用场景,其实很简单:

  • 如果你要创建的是引用类型的数据,比如对象、数组、Map、Set,优先用reactive,因为访问属性不需要加.value,写起来更顺手;
  • 如果你要创建的是基本数据类型的数据,比如数字、字符串、布尔值、null、undefined,必须用ref,因为Proxy代理不了基本类型;
  • 如果你要创建的是引用类型的数据,但可能会把整个数据换掉,比如一开始是个空对象,后来会赋值成{ a: 1 },这时候也建议用ref,因为用reactive的话,直接赋值整个对象会丢失响应式。

举个例子:

import { reactive, ref } from 'vue'
// 引用类型,一般用reactive
const user = reactive({
  name: '张三',
  age: 18,
  hobbies: ['读书', '跑步']
})
// 基本类型,必须用ref
const count = ref(0)
// 可能会换掉整个引用的,建议用ref
const currentComponent = ref(null) // 这是ref的另一个常用场景:绑定DOM元素
const dynamicData = ref({})
// 后来换掉整个对象,响应式还在
dynamicData.value = { type: 'article', title: 'Vue3 observable详解' }

怎么正确解构reactive对象?

刚才说了,reactive只能代理引用类型,如果你直接用ES6的解构赋值去解构reactive对象,解构出来的基本类型属性会丢失响应式,这也是初学者最容易踩的坑之一。

那怎么解决这个问题呢?有两个常用的方法: 第一个方法是用toRefs把reactive对象的所有属性都转成ref,然后再解构,这样解构出来的属性还是响应式的,不过访问的时候要加.value(如果是在template里用的话,不需要加,Vue3会自动帮你解包); 第二个方法是用reactive对象本身,不要解构,或者只在不需要响应式的地方解构。

举个例子:

import { reactive, toRefs } from 'vue'
const user = reactive({
  name: '张三',
  age: 18
})
// 直接解构,name和age都会丢失响应式
const { name, age } = user
// 用toRefs转换后再解构,name和age还是响应式的
const { name: refName, age: refAge } = toRefs(user)
// 修改refName.value,user.name也会变
refName.value = '李四'

怎么创建全局的响应式数据?

在Vue2里,我们可以用Vue.observable创建全局的响应式数据,然后在各个组件里引用,在Vue3里,我们可以用reactive或者ref创建全局的响应式数据,不过更推荐的是用Vue官方提供的Pinia状态管理库,因为Pinia更适合大型项目,有更好的类型支持、更好的调试体验、更容易维护。

不过如果是小型项目,或者只是想临时做个简单的全局响应式数据管理,用reactive或者ref也是完全可以的,比如我们可以创建一个store.js文件:

import { reactive } from 'vue'
export const globalStore = reactive({
  count: 0,
  increment() {
    this.count++
  },
  decrement() {
    this.count--
  }
})

然后在各个组件里直接import引用就行,修改globalStore的属性或者调用它的方法,所有引用的组件界面都会更新。

今天咱们聊了Vue3 observable的前世今生,知道了它其实就是reactive的兼容写法,本质上没有任何区别;也聊了reactive的核心原理是Proxy,以及Proxy的几个局限性;还聊了日常开发里用reactive的实用小技巧,包括什么时候用reactive什么时候用ref、怎么正确解构reactive对象、怎么创建全局的响应式数据。

最后再提醒大家一句:虽然Vue3核心包里还留着observable的兼容代码,但从长远来看,大家最好还是用reactive,避免以后升级Vue版本的时候出现问题。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门