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

10,690次阅读
没有评论

共计 11387 个字符,预计需要花费 29 分钟才能阅读完成。

#ref 和 reactive 的区别

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



 

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

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(value: T): Ref>

export function ref(): Ref

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 {

  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 = 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 = (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 需要通过 .value 访问数据,而 reactive() 可以直接用作常规对象。

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

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

前言

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

ref 和 reactive 的区别

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



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

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

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(value: T): Ref>

export function ref(): Ref

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 {

  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 = 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 = (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 需要通过 .value 访问数据,而 reactive() 可以直接用作常规对象。

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

ref 类型为 Ref,而 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。

原文地址: vue 中 ref()与 reactive(的区别)

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于2024-11-01发表,共计11387字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)