首页 > 其他分享 >vue中ref()与reactive(的区别)

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

时间:2024-07-13 11:28:26浏览次数:9  
标签:count vue const value reactive console ref

#ref和reactive的区别

对比之前先看一下如何使用,它们的使用方法都很简单,也很类似:

<template>

  <div>{{user.first_name}} {{user.last_name}}</div>

  <div>{{ age }}</div>

</template>

<script>

import { reactive } from 'vue'

export default {

  setup() {

    const age = ref(18)

    const user = reactive({

      first_name: "Karl",

      last_name: "Max",

    })

    return { user , age}

  }

}

</script>

 

#接下来我们就来分析一下它们的不同点:

1.可接受的原始数据类型不同

ref() 和reactive()都是接收一个普通的原始数据,再将其转换为响应式对象,例如上面代码中的user和age。却别在于:ref可以同时处理基本数据类型和对象,而reactive只能处理处理对象而支持基本数据类型。

const numberRef = ref(0); // OK

const objectRef = ref({ count: 0 }) // OK



//TS2345: Argument of type 'number' is not assignable to parameter of type 'object'.

const numberReactive = reactive(0);

const objectReactive = reactive({ count: 0}); // OK

2.这是因为二者响应式数据实现的方式不同:

ref是通过一个中间对象RefImpl持有数据,并通过重写它的set和get方法实现数据劫持的,本质上依旧是通过Object.defineProperty 对RefImpl的value属性进行劫持。

reactive则是通过Proxy进行劫持的。Proxy无法对基本数据类型进行操作,进而导致reactive在面对基本数据类型时的束手无策。

ref对应的源码如下:

export function ref<T>(value: T): Ref<UnwrapRef<T>>

export function ref<T = any>(): Ref<T | undefined>

export function ref(value?: unknown) {

  return createRef(value, false)

}



function createRef(rawValue: unknown, shallow: boolean) {

  if (isRef(rawValue)) {

    return rawValue

  }

  return new RefImpl(rawValue, shallow)

}



class RefImpl<T> {

  private _value: T

  private _rawValue: T





  constructor(value: T, public readonly __v_isShallow: boolean) {

    this._rawValue = __v_isShallow ? value : toRaw(value)

    this._value = __v_isShallow ? value : toReactive(value)

  }



 /**

   * 重写get和set方法,

   * 本质上是通过Object.defineProperty 

   * 对属性value进行劫持*/

  get value() {

    //收集依赖

    track()

    return this._value

  }



  set value(newVal) {

    if (hasChanged(newVal, this._rawValue)) {

      this._value = newVal

      //触发依赖

      trigger()

    }

  }

}

删减整合后的reactive代码如下:

export function reactive(target: object) {

  return createReactiveObject(

    target,

    false

  )

}



function createReactiveObject(

  target: Target,

  isReadonly: boolean

) {

  const proxy = new Proxy(

    target,

    {

      get(target, key) {

   //收集依赖

        track()

        return target[propKey]

      },

      set(target, key,value) {

        target[propKey] = value

        //触发依赖

        trigger()

      }

    }

  )

  return proxy

}

Object.defineProperty、Proxy和数据劫持的相关内容详情可见:Vue3数据劫持优化。

总结:ref可以存储基本数据类型而reactive则不能

返回值类型不同

运行如下代码:

const count1 = ref(0)

const count2 = reactive({count:0})

console.log(count1)

console.log(count2)

输出结果为:

RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: 0, _value: 0}

Proxy(Object) {count: 0}

ref()返回的是一个持有原始数据的RefImpl实例。而reactive()返回的类型则是原始数据的代理Proxy实例

因此,在定义数据类型时,有些许差别:

interface Count {

  num:number

}

const countRef:Ref<number> = ref(0)

const countReactive: Count = reactive({num:1})

另外如果reactive中有响应式对象,它会被自动展开,所以下面代码是正确的:

const countReactiveRef: Count = reactive({num:ref(2)})

结论:ref(value: T)返回的Ref 类型,而reactive(object: T)返回的T 类型的代理

访问数据的方式不同

返回值的类型不同,就会导致数据的访问方式不同。通过上文的可知:

ref()返回的是RefImpl的一个实例对象,该对象通过_value私有变量持有原始数据,并重写了value的get方法。因此,当想要访问原始对象的时候,需要通过xxx.value的方式触发get函数获取数据。同样的,在修改数据时,也要通过xxx.value = yyy的方式触发set函数。

reactive() 返回的是原始对象的代理,代理对象具有和原始对象相同的属性,因此我们可以直接通过.xxx的方式访问数据

反应在代码中如下:

const objectRef = ref({ count: 0 });

const refCount = objectRef.value.count;



const objectReactive = reactive({ count: 0}); 

const reactiveCount = objectReactive.count;

总结:ref需要通过value属性间接的访问数据(在templates中vue做了自动展开,可以省略.value),而reactive可以直接访问。

原始对象的可变性不同

ref通过一个RefImpl实例持有原始数据,进而使用.value属性访问和更新。而对于一个实例而言,其属性值是可以修改的。因此可以通过.value的方式为ref重新分配数据,无需担心RefImpl实例被改变进而破坏响应式:

const count = ref({count:1})

console.log(count.value.count)

//修改原始值

count.value = {count:3}

console.log(count.value.count)

//修改原始值

count.value = {name:"Karl"}

console.log(count.value.count)

console.log(count.value.name)



//输出如下:

//1

//3

//undefined

//karl

而 reactive返回的是原始对象的代理,因此不能对其重新分配对象,只能通过属性访问修改属性值,否则会破坏掉响应式:

let objectReactive = reactive({ count: 0})

effect(() => {

  console.log(`数据变化了:${objectReactive.count}`)

})

//可以正常修改值

objectReactive.count = 1

objectReactive.count = 2

//修改objectReactive之后effect不再会接收到数据变化的通知

objectReactive = {count:3}

objectReactive.count = 4

console.log("结束了")

//输出如下:

//数据变化了:0

//数据变化了:1

//数据变化了:2

//结束了

原因很简单:effect函数监听的是原始值{ count: 0}的代理objectReactive,此时当通过该代理修改数据时,可以触发回调。但是当程序运行到objectReactive = {count:3}之后,objectReactive 的指向不再是{ count: 0}的代理了,而是指向了新的对象{count:3}。这时objectReactive.count = 4修改的不再是effect 所监听的代理对象,而是新的普通的不具备响应式能力的对象{count:3}。effect就无法监听到数据的变化了,objectReactive响应式能力也因此而被破坏了。

如果你直接修改ref的指向,ref的响应式也会失效:

let count = ref(0)

effect(() => {

  console.log(`数据变化了:${count.value}`)

})

count.value = 1

count = ref(0)

//effect不会监听到此处的变化

count.value = 2

console.log("结束了")

结论:可以给ref的值重新分配给一个新对象,而reactive只能修改当前代理的属性

ref借助reactive实现对Object类型数据的深度监听

结合上文的RefImpl的源码:

constructor(value: T, public readonly __v_isShallow: boolean) {

    this._rawValue = __v_isShallow ? value : toRaw(value)

    this._value = __v_isShallow ? value : toReactive(value)

}



export const toReactive = <T extends unknown>(value: T): T =>

  isObject(value) ? reactive(value) : value

ref在发现被监听的原始对象是Object类形时,会将原始对象转换成reactive并赋值给_value属性。而此时ref.value返回的并不是原始对象,而是它的代理。

通过如下代码验证:

const refCount = ref({count:0})

console.log(refCount.value)

//输出结果:

//Proxy(Object) {count: 0}

结论:``ref()在原始数据位Object类形时,会通过reactive包装原始数据后再赋值给_value。

对侦听属性的影响不同

执行如下代码:

let refCount = ref({count:0})

watch(refCount,() => {

     console.log(`refCount数据变化了`)

})

refCount.value = {count:1}

//输出结果:

//refCount数据变化了

watch()可以检测到ref.value的变化。然而,继续执行如下代码

let refCount = ref({count:0})

watch(refCount,() => {

    console.log(`refCount数据变化了`)

})

refCount.value.count = 1



let reactiveCount = reactive({count:0})

watch(reactiveCount,() => {

    console.log(`reactiveCount数据变化了`)

})

reactiveCount.count = 1

//输出结果

//reactiveCount数据变化了

这次watch()没有监听到refCount的数据变化——watch()默认情况下不会深入观察 ref。若要watch深入观察ref,则需要修改参数如下:

watch(refCount, () => { 

  console.log('reactiveCount数据变化了!')

}, { deep: true })

而对于reactive而言,无论你是否声明deep: true,watch都会深入观察。

结论:watch()默认情况下只监听ref.value的更改,而对reactive执行深度监听。

总结和用法

ref可以存储原始类型,而reactive不能。

ref需要通过<ref>.value访问数据,而reactive()可以直接用作常规对象。

可以重新分配一个全新的对象给ref的value属性,而reactive()不能。

ref类型为Ref<T>,而reactive返回的反应类型为原始类型本身。5516人阅读

前言

你一定知道Vue中的响应式编程,它提供了在数据变化时自动更新UI的能力,摒弃了传统的数据更新时手动更新UI的方式。在Vue 3.0之前,我们定义在data函数中的数据会被自动转换为响应式。而在 Composition API 中,还有两种方式让我们定义响应式对象:ref() 和reactive()。 但是,他们有什么不同之处呢?

ref和reactive的区别

对比之前先看一下如何使用,它们的使用方法都很简单,也很类似:

<template>

  <div>{{user.first_name}} {{user.last_name}}</div>

  <div>{{ age }}</div>

</template>

<script>

import { reactive } from 'vue'

export default {

  setup() {

    const age = ref(18)

    const user = reactive({

      first_name: "Karl",

      last_name: "Max",

    })

    return { user , age}

  }

}

</script>

接下来我们就来分析一下它们的不同点:

可接受的原始数据类型不同

ref() 和reactive()都是接收一个普通的原始数据,再将其转换为响应式对象,例如上面代码中的user和age。却别在于:ref可以同时处理基本数据类型和对象,而reactive只能处理处理对象而支持基本数据类型。

const numberRef = ref(0); // OK

const objectRef = ref({ count: 0 }) // OK



//TS2345: Argument of type 'number' is not assignable to parameter of type 'object'.

const numberReactive = reactive(0);

const objectReactive = reactive({ count: 0}); // OK

这是因为二者响应式数据实现的方式不同:

ref是通过一个中间对象RefImpl持有数据,并通过重写它的set和get方法实现数据劫持的,本质上依旧是通过Object.defineProperty 对RefImpl的value属性进行劫持。

reactive则是通过Proxy进行劫持的。Proxy无法对基本数据类型进行操作,进而导致reactive在面对基本数据类型时的束手无策。

ref对应的源码如下:

export function ref<T>(value: T): Ref<UnwrapRef<T>>

export function ref<T = any>(): Ref<T | undefined>

export function ref(value?: unknown) {

  return createRef(value, false)

}



function createRef(rawValue: unknown, shallow: boolean) {

  if (isRef(rawValue)) {

    return rawValue

  }

  return new RefImpl(rawValue, shallow)

}



class RefImpl<T> {

  private _value: T

  private _rawValue: T





  constructor(value: T, public readonly __v_isShallow: boolean) {

    this._rawValue = __v_isShallow ? value : toRaw(value)

    this._value = __v_isShallow ? value : toReactive(value)

  }



 /**

   * 重写get和set方法,

   * 本质上是通过Object.defineProperty 

   * 对属性value进行劫持*/

  get value() {

    //收集依赖

    track()

    return this._value

  }



  set value(newVal) {

    if (hasChanged(newVal, this._rawValue)) {

      this._value = newVal

      //触发依赖

      trigger()

    }

  }

}

删减整合后的reactive代码如下:

export function reactive(target: object) {

  return createReactiveObject(

    target,

    false

  )

}



function createReactiveObject(

  target: Target,

  isReadonly: boolean

) {

  const proxy = new Proxy(

    target,

    {

      get(target, key) {

   //收集依赖

        track()

        return target[propKey]

      },

      set(target, key,value) {

        target[propKey] = value

        //触发依赖

        trigger()

      }

    }

  )

  return proxy

}

Object.defineProperty、Proxy和数据劫持的相关内容详情可见:Vue3数据劫持优化。

总结:ref可以存储基本数据类型而reactive则不能

返回值类型不同

运行如下代码:

const count1 = ref(0)

const count2 = reactive({count:0})

console.log(count1)

console.log(count2)

输出结果为:

RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: 0, _value: 0}

Proxy(Object) {count: 0}

ref()返回的是一个持有原始数据的RefImpl实例。而reactive()返回的类型则是原始数据的代理Proxy实例

因此,在定义数据类型时,有些许差别:

interface Count {

  num:number

}

const countRef:Ref<number> = ref(0)

const countReactive: Count = reactive({num:1})

另外如果reactive中有响应式对象,它会被自动展开,所以下面代码是正确的:

const countReactiveRef: Count = reactive({num:ref(2)})

结论:ref(value: T)返回的Ref 类型,而reactive(object: T)返回的T 类型的代理

访问数据的方式不同

返回值的类型不同,就会导致数据的访问方式不同。通过上文的可知:

ref()返回的是RefImpl的一个实例对象,该对象通过_value私有变量持有原始数据,并重写了value的get方法。因此,当想要访问原始对象的时候,需要通过xxx.value的方式触发get函数获取数据。同样的,在修改数据时,也要通过xxx.value = yyy的方式触发set函数。

reactive() 返回的是原始对象的代理,代理对象具有和原始对象相同的属性,因此我们可以直接通过.xxx的方式访问数据

反应在代码中如下:

const objectRef = ref({ count: 0 });

const refCount = objectRef.value.count;



const objectReactive = reactive({ count: 0}); 

const reactiveCount = objectReactive.count;

总结:ref需要通过value属性间接的访问数据(在templates中vue做了自动展开,可以省略.value),而reactive可以直接访问。

原始对象的可变性不同

ref通过一个RefImpl实例持有原始数据,进而使用.value属性访问和更新。而对于一个实例而言,其属性值是可以修改的。因此可以通过.value的方式为ref重新分配数据,无需担心RefImpl实例被改变进而破坏响应式:

const count = ref({count:1})

console.log(count.value.count)

//修改原始值

count.value = {count:3}

console.log(count.value.count)

//修改原始值

count.value = {name:"Karl"}

console.log(count.value.count)

console.log(count.value.name)



//输出如下:

//1

//3

//undefined

//karl

而 reactive返回的是原始对象的代理,因此不能对其重新分配对象,只能通过属性访问修改属性值,否则会破坏掉响应式:

let objectReactive = reactive({ count: 0})

effect(() => {

  console.log(`数据变化了:${objectReactive.count}`)

})

//可以正常修改值

objectReactive.count = 1

objectReactive.count = 2

//修改objectReactive之后effect不再会接收到数据变化的通知

objectReactive = {count:3}

objectReactive.count = 4

console.log("结束了")

//输出如下:

//数据变化了:0

//数据变化了:1

//数据变化了:2

//结束了

原因很简单:effect函数监听的是原始值{ count: 0}的代理objectReactive,此时当通过该代理修改数据时,可以触发回调。但是当程序运行到objectReactive = {count:3}之后,objectReactive 的指向不再是{ count: 0}的代理了,而是指向了新的对象{count:3}。这时objectReactive.count = 4修改的不再是effect 所监听的代理对象,而是新的普通的不具备响应式能力的对象{count:3}。effect就无法监听到数据的变化了,objectReactive响应式能力也因此而被破坏了。

如果你直接修改ref的指向,ref的响应式也会失效:

let count = ref(0)

effect(() => {

  console.log(`数据变化了:${count.value}`)

})

count.value = 1

count = ref(0)

//effect不会监听到此处的变化

count.value = 2

console.log("结束了")

结论:可以给ref的值重新分配给一个新对象,而reactive只能修改当前代理的属性

ref借助reactive实现对Object类型数据的深度监听

结合上文的RefImpl的源码:

constructor(value: T, public readonly __v_isShallow: boolean) {

    this._rawValue = __v_isShallow ? value : toRaw(value)

    this._value = __v_isShallow ? value : toReactive(value)

}



export const toReactive = <T extends unknown>(value: T): T =>

  isObject(value) ? reactive(value) : value

ref在发现被监听的原始对象是Object类形时,会将原始对象转换成reactive并赋值给_value属性。而此时ref.value返回的并不是原始对象,而是它的代理。

通过如下代码验证:

const refCount = ref({count:0})

console.log(refCount.value)

//输出结果:

//Proxy(Object) {count: 0}

结论:``ref()在原始数据位Object类形时,会通过reactive包装原始数据后再赋值给_value。

对侦听属性的影响不同

执行如下代码:

let refCount = ref({count:0})

watch(refCount,() => {

     console.log(`refCount数据变化了`)

})

refCount.value = {count:1}

//输出结果:

//refCount数据变化了

watch()可以检测到ref.value的变化。然而,继续执行如下代码

let refCount = ref({count:0})

watch(refCount,() => {

    console.log(`refCount数据变化了`)

})

refCount.value.count = 1



let reactiveCount = reactive({count:0})

watch(reactiveCount,() => {

    console.log(`reactiveCount数据变化了`)

})

reactiveCount.count = 1

//输出结果

//reactiveCount数据变化了

这次watch()没有监听到refCount的数据变化——watch()默认情况下不会深入观察 ref。若要watch深入观察ref,则需要修改参数如下:

watch(refCount, () => { 

  console.log('reactiveCount数据变化了!')

}, { deep: true })

而对于reactive而言,无论你是否声明deep: true,watch都会深入观察。

结论:watch()默认情况下只监听ref.value的更改,而对reactive执行深度监听。

总结和用法

ref可以存储原始类型,而reactive不能。

ref需要通过<ref>.value访问数据,而reactive()可以直接用作常规对象。

可以重新分配一个全新的对象给ref的value属性,而reactive()不能。

ref类型为Ref<T>,而reactive返回的反应类型为原始类型本身。

watch默认只观察ref的value,而对reactive则执行深度监听。

ref默认会用reactive 对象类型的原始值进行深层响应转换。

使用习惯:虽然没有规则规定要在何时使用ref或者reactive,亦或是混合使用。这些完全取决于开发者的编程习惯。但是为了保持代码的一致性和可读性,我倾向于使用ref而非reactive。

vue.jsjavascript前端

来自专栏

Vue.js

著作权归作者所有

watch默认只观察ref的value,而对reactive则执行深度监听。

ref默认会用reactive 对象类型的原始值进行深层响应转换。

使用习惯:虽然没有规则规定要在何时使用ref或者reactive,亦或是混合使用。这些完全取决于开发者的编程习惯。但是为了保持代码的一致性和可读性,我倾向于使用ref而非reactive。

标签:count,vue,const,value,reactive,console,ref
From: https://blog.csdn.net/lh15766518127/article/details/140316392

相关文章

  • vue 实现两个时间戳 相减得到一个计时器
     <template><div>距离结束还剩{{formattedTime}}</div></template><script>  exportdefault{  data(){   return{     endTime:newDate('2029-07-17T00:00:00').getTime(),//结......
  • 如何在 Vue 项目中优雅地使用图标
    1.字体图标与矢量图标目前主要有两种图标类型:字体图标和矢量图标。字体图标是在网页打开时,下载一整个图标库,通常可以通过特定标签例如<i>来使用,优点是方便地实现文字混排,缺点是包体积大,且难以自定义。矢量图标本质是<svg>标签,包中只含有所需的图标,且很容易自定义,也可以选......
  • 基于java+springboot+vue实现的作业管理系统(文末源码+Lw)110
    基于SpringBoot+Vue的实现的作业管理系统(源码+数据库+万字Lun文+流程图+ER图+结构图+演示视频+软件包)功能描述:作业管理系统有管理员,教师,学生三个角色。教师和学生都可以进行注册然后再登录。学生可以修改自己的密码,查看和下载作业信息,并且可以提交自己写好的作业,并且可以......
  • 基于java+springboot+vue实现的新闻稿件管理系统(文末源码+Lw)109
     基于SpringBoot+Vue的实现的新闻稿件管理系统(源码+数据库+万字Lun文+流程图+ER图+结构图+演示视频+软件包)系统功能:新闻稿件管理系统管理员功能有个人中心,用户管理,记者管理,审批员管理,新闻分类管理,新闻信息管理,系统管理等。记者发布新闻信息,审批员进行审核,用户进行查看。因......
  • 免费分享一套SpringBoot+Vue农产品在线销售(在线商城)管理系统【论文+源码+SQL脚本】,
    大家好,我是java1234_小锋老师,看到一个不错的SpringBoot+Vue农产品在线销售(在线商城)管理系统,分享下哈。项目介绍如今社会上各行各业,都喜欢用自己行业的专属软件工作,互联网发展到这个时候,人们已经发现离不开了互联网。新技术的产生,往往能解决一些老技术的弊端问题。因为传统......
  • 基于java+springboot+vue实现的在线试题库系统(文末源码+Lw)108
     基于SpringBoot+Vue的实现的在线试题库系统(源码+数据库+万字Lun文+流程图+ER图+结构图+演示视频+软件包) 系统功能:精品在线试题库系统有管理员,教师,学生三个角色。管理员功能有个人中心,专业管理,学生管理,教师管理,试卷管理,试题管理,考试管理。教师可以管理试题和试卷,查看学生......
  • Vue2-利用自定义指令实现按钮权限控制
    文件结构新建directive目录,并在该目录下新增permission目录,在permission目录下新增hasPerm.js和index.js。文件内容hasPerm.js//操作按钮权限控制importstorefrom"@/store";exportdefault{/***被绑定元素插入父节点时调用*@param{*}el指令绑定的元素......
  • Vue.js Ajax(axios)
     Vue.js2.0版本推荐使用axios来完成ajax请求。Axios是一个基于Promise的HTTP库,可以用在浏览器和node.js中。Github开源地址: https://github.com/axios/axios安装方法使用cdn:<scriptsrc="https://unpkg.com/axios/dist/axios.min.js"></script>或<scri......
  • Vue.js Ajax(vue-resource)
     Vue要实现异步加载需要使用到vue-resource库。Vue.js2.0版本推荐使用 axios 来完成ajax请求。<scriptsrc="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>Get请求以下是一个简单的Get请求实例,请求地址是一个简单的txt文......
  • Vue.js Ajax(axios)
    Vue.js2.0版本推荐使用axios来完成ajax请求。Axios是一个基于Promise的HTTP库,可以用在浏览器和node.js中。Github开源地址: https://github.com/axios/axios安装方法使用cdn:<scriptsrc="https://unpkg.com/axios/dist/axios.min.js"></script>或<script......