Vue3中ref()与reactive()的区别

237 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

在vue3中,如果我们想让数据在页面中响应式地进行更新,一般会用到ref()和reactive()这两个函数。

1.ref : ref允许我们创建一个任意类型的响应式的ref对象,在使用时需要带上.value。
const a = ref(99)
a.value++
console.log(a.value) // 100

const b = ref({count:99})
b.value.count++
console.log(b.value.count) // 100 

在模板中使用ref对象时,假如ref位于顶层,那么就不需要使用value,也就是说,它会自动解包,但如果ref对象是作为一个属性声明于对象之中,在模板中进行运算时仍然要使用.value 。

<script setup>
import { ref } from 'vue'

const name = ref(99)
const nameObj ={ count: ref(99) }
console.log(nameObj.count.value) // 99
</script>

<template>
 <h1>{{ name }}</h1> // 页面显示99,自动解包
 <h1>{{ nameObj.count }}</h1> // 页面显示99,作为插值自动解包
 <h1>{{ nameObj.count + 1 }}</h1> // 页面显示[object Object]1
 <h1>{{ nameObj.count.value + 1 }}</h1> // 页面显示100
</template>

可以看到,直接在页面中使用b.count计算是无法正常显示的,因为b.count本身是一个ref对象,并不位于顶层,所以没有自动解包。

2.reactive : 通常使用reactive()来创建一个响应式的对象或数组,这样的对象或数组状态都是默认深层响应式的。
import { reactive } from 'vue'

const name = reactive({
 count:99
})
name.count++
console.log(name.count) // 100

const nameObj = reactive({
 obj:{
   arr:['Alice','Bob','Carol']
 }
})
nameObj.obj.arr.push('Dave')
console.log(nameObj.obj.arr) // ['Alice','Bob','Carol','Dave'] 

不管他们嵌套多深,都可以被追踪到,例如:

reactive也有一些局限性,那就是仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的原始类型无效(见官网),并且假如用一个新对象替换了原来的旧对象,那么原来的旧对象会失去响应性,例如 :

const nameObj = reactive({
 count:123
})
nameObj = reactive({
 count:456
}) // { count: 123 } 失去了响应性

Reactive的本质是将每一层的数都解析成proxy对象,Reactive 的响应式默认都是递归的,改变某一层的值都会递归的调用一遍,重新渲染dom。

3.两者的异同

相同点 : 创建响应式对象

不同点 :

  1. ref可接受对象类型也可以接受基本类型,而reactive只能接收对象或数组等复杂类型
  2. ref创建的数据返回类型为RefImpl ,而RefImpl._value是一个 reactive 代理的原始对象,reactive创建的数据返回类型为Proxy
  3. ref使用.value来访问值
  4. Ref在模板调用可以直接省略value,在方法中改变变量的值需要修改value的值,才能修改成功。Reactive在模板必须写全不然显示整个数据。
4.差异
1.Ref与Reactive创建的都是递归响应的,将每一层的json 数据解析成一个proxy对象,shallowRef 与shallowReactive创建的是非递归的响应对象,shallowReactive创建的数据第一层数据改变会重新渲染dom
var state = shallowReactive({
    a: 'a',
    gf:{
        b:'b',
        f:{
           c:'c',
           s:{d:'d'}
       }
   })
state.a =  '1'
// 改变第一层的数据会导致页面重新渲染`
// state => Proxy {a:"a",gf:{...}}`
// 如果不改变第一层 只改变其他的数据 页面不会重新渲染 例如 state.gf.b = 2`

通过shallowRef创建的响应式对象,需要修改整个value才能重新渲染dom。

var state = shallowRef({
   a:'a',
    gf:{
       b:'b',
       f:{
          c:'c',
          s:{d:'d'}
       }
    }
})
state.value.a = 1
/*
并不能重新渲染,shallowRef的原理也是通过shallowReactive({value:{}})创建的,要修改value才能重新渲染
*/
state.value = {
    a:'1',
    gf:{
       b:'2',
       f:{
          c:'3',
          s:{d:'d'}
       }
    }
}

如果使用了shallowRef想要只更新某一层的数据可以使用triggerRef

var state = shallowRef({
   a:'a',
    gf:{
       b:'b',
       f:{
          c:'c',
          s:{d:'d'}
       }
    }
})
state.value.gf.f.s.d = 4;
triggerRef(state);

页面就会重新渲染

2.toRaw ---只修改数据不渲染页面

如果只想修改响应式的数据不想引起页面渲染可以使用toRaw这个方法。

var obj = {name:'test'};
var state = reactive(obj);
var obj2 = toRaw(state);
obj2.name = 'zs'; // 并不会引起页面的渲染

----
//如果是用ref 创建的 就要获取value值
var obj = {name:'test'};
var state = ref(obj);
var obj2 = toRaw(state.value);
3.markRaw --- 不追踪数据

如果不想要数据被追踪,变成响应式数据可以调用这个方法,就无法 追踪修改数据重新渲染页面

var obj = {name:'test'};
obj = markRaw(obj);
var state = reactive(obj);
state.name = 'zs'; // 无法修改数据 页面也不会修改
5.toRef --- 跟数据源关联 不修改UI

如果使用ref 创建的响应式变量,不会与源数据关联,如果想要与源数据关联但数据更改不更新UI,就要使用toRef创建

var obj = {name:'test'};
var state = ref(obj.name);
state.name = 'zs'; // 此时obj的name 属性值并不会改变,UI会自动更新

//
var obj = {name:'test'};
var state = toRef(obj,'name'); // 只能设置一个属性值
state.name = 'zs'; // 此时obj里面的name属性值会发生改变,但是UI 不会更新
6.toRefs ---设置多个toRef属性值

如果想要设置多个toRef属性值,可以使用toRefs

var obj = {name:'test',age:16};
var state = toRefs(obj);
state.name.value = 'zs'; // obj的name的属性值也会改变,但UI不会更新
state.age.value = 18; // obj的age的属性值也会改变,但UI不会更新
7.customRef ---自定义一个ref

通过customRef这个方法可以自定义一个响应式的ref方法

function myRef(value){
   /*
    customRef函数返回一个对象,对象里面有2个方法,get/set方法,创建的对象获取数据的时候能 访问到get方法,创建的对象修改值的时候会触发set 方法
    customRef函数有2个参数,track/trigger,track参数是追踪的意思,get 的方法里面调用,可以随时追踪数据改变。trigger参数 是触发响应的意思,set 方法里面调用可以更新UI界面
   */
    return customRef((track,trigger)=>{
       return {
          get(){
             track() // 追踪数据
             return value     
          },
          set(newVal){
             value = newVal
             trigger() // 更新UI界面
          }
       }
    })

}

setup(){
   var age = myRef(18)
   age.value = 20
}
8.ref 捆绑页面的标签

vue2.0 可以通过this.refs拿到dom元素,vue3取消了这一操作,没有了refs拿到dom 元素,vue3取消了这一操作,没有了refs拿到dom元素,vue3取消了这一操作,没有了refs这个属性值,可以直接用ref()方法生成响应式变量与dom 元素捆绑

<template>
   <div ref="box"></div>
</template>
import {ref, onMounted} from 'vue'
/*
 setup 方法调用是在生命周期beforeCreate与created 之间
*/
<script>
   setup(){
      var box = ref(null);
      onMounted(()=>{
         console.log('onMounted',box.value)
      });
      console.log(box.value)
      return {box}

   }

</script>

【更多相关Vue3.0 Ref与Reactive区别 后续】

【如修改请评论】