vue2升级vue3之ref和reactive

386 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。

vue2响应式

 <template>
   <div>
     <p>{{name}}</p>
   </div>
 </template>
 ​
 <script>
 export default {
   name: 'Home',
   data() {
     return {
       name: 'YnacyZhang'
     }
   },
   created() {
     setTimeout(() => {
       this.name = "Hello World!"
     }, 1000)
   },
 }
 </script>
 ​

我们在data选项中返回一个对象,当我们修改data中的数据时,页面也会相应的改变,即响应式。

vue2响应式基本原理:

1.通过Object.defineProperty来实现监听数据的改变和读取(属性中的getter和setter方法) 实现数据劫持

2.观察者模式(发布者-订阅者) 观察者(订阅者) – Watcher: update():当事件发生时,具体要做的事情目标(发布者) – Dep: ①subs 数组:存储所有的观察者 ②addSub():添加观察者 ③notify():当事件发生,调用所有观察者的 update() 方法

3.当数据发生改变通过发布者订阅者模式来进行通知进行界面刷新

vue3响应式

Vue 2 使用 getter / setters 完全是出于支持旧版本浏览器的限制。而在 Vue 3 中则使用了Proxy来创建响应式对象,仅将 getter / setter 用于 ref。

 function reactive(obj) {
   return new Proxy(obj, {
     get(target, key) {
       track(target, key)
       return target[key]
     },
     set(target, key, value) {
       target[key] = value
       trigger(target, key)
     }
   })
 }
 ​
 function ref(value) {
   const refObject = {
     get value() {
       track(refObject, 'value')
       return value
     },
     set value(newValue) {
       value = newValue
       trigger(refObject, 'value')
     }
   }
   return refObject
 }

在vue3组合式API中,主要使用reactiveref来创建响应式数据。

用 reactive()函数创建响应式数据

我们可以使用 reactive() 函数创建一个响应式对象或数组:

 import { reactive } from 'vue'
 ​
 const state = reactive({ count: 0 })

在模板中使用:

 <div>{{ state.count }}</div>

深层响应式

在 Vue 中,状态都是默认深层响应式的。这意味着即使在更改深层次的对象或数组,你的改动也能被检测到。

 import { reactive } from 'vue'
 ​
 const obj = reactive({
   nested: { count: 0 },
   arr: ['foo', 'bar']
 })
 ​
 function mutateDeeply() {
   // 以下都会按照期望工作
   obj.nested.count++
   obj.arr.push('baz')
 }

reactive()的局限性

  1. 仅对对象类型有效(对象、数组和 MapSet 这样的集合类型),而对 stringnumberboolean 这样的 原始类型 无效。

  2. 因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失:

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

    同时这也意味着当我们将响应式对象的属性赋值解构至本地变量时,或是将该属性传入一个函数时,我们会失去响应性。

toRefs()

将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。

当从组合式函数中返回响应式对象时,toRefs 相当有用。使用它,消费者组件可以解构/展开返回的对象而不会失去响应性:

 function useFeatureX() {
   const state = reactive({
     foo: 1,
     bar: 2
   })
 ​
   // ...基于状态的操作逻辑
 ​
   // 在返回时都转为 ref
   return toRefs(state)
 }
 ​
 // 可以解构而不会失去响应性
 const { foo, bar } = useFeatureX()
 ​

用ref()定义响应式变量

reactive() 的种种限制归根结底是因为 JavaScript 没有可以作用于所有值类型的 “引用” 机制。为此,Vue 提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref

 import { ref } from 'vue'
 ​
 const count = ref(0)

ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象,所以访问它的值需要使用count.value

 const count = ref(0)
 ​
 console.log(count) // { value: 0 }
 console.log(count.value) // 0
 ​
 count.value++
 console.log(count.value) // 1

基本类型的数据:响应式依然是靠Object.defineProperty()的get与set完成的。

对象类型的数据:内部 “ 求助 ” 了reactive函数。

值为对象类型

和响应式对象的属性类似,ref 的 .value 属性也是响应式的。同时,当值为对象类型时,会用 reactive() 自动转换它的 .value

一个包含对象类型值的 ref 可以响应式地替换整个对象:

 const objectRef = ref({ count: 0 })
 ​
 // 这是响应式的替换
 objectRef.value = { count: 1 }

ref 在模板中的解包

当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value

 <script setup>
 import { ref } from 'vue'
 ​
 const count = ref(0)
 ​
 function increment() {
   count.value++
 }
 </script>
 ​
 <template>
   <button @click="increment">
     {{ count }} <!-- 无需 .value -->
   </button>
 </template>

请注意,仅当 ref 是模板渲染上下文的顶层属性时才适用自动“解包”。 例如, foo 是顶层属性,但 object.foo 不是。

 const object = { foo: ref(1) }
 {{ object.foo + 1 }}

渲染的结果会是一个 [object Object],因为 object.foo 是一个 ref 对象。

ref 在响应式对象中的解包

当一个 ref 被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样:

 const count = ref(0)
 const state = reactive({
   count
 })
 ​
 console.log(state.count) // 0
 ​
 state.count = 1
 console.log(count.value) // 1

如果将一个新的 ref 赋值给一个关联了已有 ref 的属性,那么它会替换掉旧的 ref:

 const otherCount = ref(2)
 ​
 state.count = otherCount
 console.log(state.count) // 2
 // 原始 ref 现在已经和 state.count 失去联系
 console.log(count.value) // 1

数组和集合类型的 ref 解包

跟响应式对象不同,当 ref 作为响应式数组或像 Map 这种原生集合类型的元素被访问时,不会进行解包

 const books = reactive([ref('Vue 3 Guide')])
 // 这里需要 .value
 console.log(books[0].value)
 ​
 const map = reactive(new Map([['count', ref(0)]]))
 // 这里需要 .value
 console.log(map.get('count').value)