Published on

理解reactive、isReactive、shallowReactive

Authors
  • avatar
    Name
    Deng Hua
    Twitter

Vue 3 中的响应式系统是其数据双向绑定的核心,它允许在响应式状态发生变化时自动更新 DOM。除了 ref 之外,还有 reactive 方法可用于创建响应式对象。

reactive 的作用是将普通对象转换为响应式对象。它递归地将对象的所有属性转换为响应式数据。它返回的是一个Proxy对象。

目录

reactive

reactive 的参数只能是对象、数组或集合类型(例如 MapSet)。当对象内的属性在更改时将自动触发视图更新。

用法:

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

  const person = reactive({
    name: 'Echo',
    age: 25,
  })

  console.log(person.name); // 'Echo'
  person.age = 28;          // change property
  console.log(person.age);  // 28
  console.log(person);
</script>

查看控制台,person将打印出

我们可以看到,打印出来的是一个 Proxy 对象。因为reactive的响应式基于 ES6的Proxy。

不过Proxy有几个特点我们需要注意:

  • reactive() 函数返回原始对象的Proxy,它不等于原始对象。
<script setup>
  import { reactive } from 'vue'

  const raw = {}
  const proxy = reactive(raw)

  console.log(proxy === raw) // false

</script>

当原对象内部的数据发生变化时,会影响proxy对象;同样,当proxy对象内部的数据发生变化时,对应的原始数据也会发生变化。

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

  const obj = {
    count: 1
  }
  const proxy = reactive(obj);

  proxy.count++;
  console.log(proxy.count); // 2
  console.log(obj.count);   // 2
</script>

想一下,当原对象内部的数据发生变化时,会影响到代理对象;当代理对象内部的数据发生变化时,相应的原始对象数据也会发生变化,那在实际开发中,我们应该操作原始对象还是代理对象呢?

答案是:代理对象,因为代理对象是反应式的。

为了确保对代理的访问一致,在同一原始对象上调用 reactive() 将始终返回相同的代理对象,而在现有代理对象上调用 reactive() 将返回代理本身:

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

  const raw = {}
  const proxy1 = reactive(raw)
  const proxy2 = reactive(raw)

  console.log(proxy1 === proxy2) // true
  console.log(reactive(proxy1) === proxy1) // true
</script>

注意:用 reactive 定义的响应式对象将深度watch对象每个层级的属性,影响所有嵌套属性。换句话说:响应式对象在保持响应式的同时也会深度解包任何 ref 属性。

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

  let obj = reactive({
    name: 'Echo',
    a: {
      b: {
        c: 1
      }
    }
  })

  console.log("obj: ", obj)
  console.log("obj.name: ", obj.name)
  console.log("obj.a: ", obj.a)
  console.log("obj.a.b: ", obj.a.b)
  console.log("obj.a.b.c: ",obj.a.b.c)
</script>

返回的对象以及嵌套在其中的对象都将用 Proxy 包装。

** isReactive

isReactive() 用于检测某物是否属于 reactive 类型;如果是,则返回 true ,否则返回 false 。

用法:

<script setup>
    const refObj = ref({
      name: 'xxxx'
    })
    const refObj1 = ref(3)
    const reactiveObj = reactive({
      name: 'wnxx'
    })
    const refObj2 = shallowRef({
      name: 'xxxx'
    })
    const reactiveObj1 = readonly(
      reactive({
        name: 'xxxx'
      })
    )
    const reactiveObj2 = shallowReactive({
      name: 'xxxx'
    })
    const refObj3 =  readonly(
      ref({
        name: 'xxxx'
      })
    )
    console.log(isReactive(refObj.value)) // true
    console.log(isReactive(refObj1)) // false
    console.log(isReactive(reactiveObj)) // true
    console.log(isReactive(refObj2.value)) // false
    console.log(isReactive(reactiveObj1)) // true
    console.log(isReactive(reactiveObj2)) // true
    console.log(isReactive(refObj3.value)) // true
</script>

注意:如果代理是使用 readonly() 创建的,但包装了另一个使用 reactive 创建的代理对象,它也会返回 true

shallowReactive

shallowReactive() 用于创建响应式对象。 shallowReactive() 会跟踪对象内属性的响应式,但忽略对象内嵌套属性的响应式更新。任何使用 ref 的属性都不会被代理自动解包。

import { shallowReactive} from 'vue'

setup(){
    let person = shallowReactive({
        name:'xxxx',
        address:{
          adName: 'adxxxx'
        }
    })
}
  • 浅层数据结构应该仅用于组件中的根级状态。避免将它们嵌套在深层的响应式对象中,因为它们创建的树具有不同的响应式,这可能难以理解和调试。

  • 在处理对象数据中的浅层和深层数据时,请谨慎使用!

  • shallowReactive() 通常在我们需要创建响应式对象,但又不希望其嵌套属性能够响应式更新时使用。

ref 和 reactive之间的区别

  • ref 主要用于创建单个响应式数据,而 reactive 用于创建具有多个响应式属性的对象。

  • 对于定义基本类型(例如number和boolean)的变量,建议使用 ref ;而对于对象或数组的响应式, 建议选择reactive

  • reactive 会递归地将对象的所有属性转换为响应式数据。

  • 在模板中使用响应式数据时,不需要使用 .value。使用 reactive 类型数据时,可以直接使用对象的属性名称来读取或写入。

  • ref 返回由 RefImpl 类构造的对象,而 reactive 使用 Proxy 返回原始对象的响应式代理。

watch 用于监听 ref 和 reactive 的区别:

  1. 使用 watch 监听 ref 定义的响应式数据(原始数据类型)
<script setup>
  import { ref, watch } from 'vue'

  let count = ref(0)
  watch(count, (newValue, oldValue) => {
    console.log(`new:${newValue},old:${oldValue}`)
  })
  const changeCount = () => {
    count.value += 10;
  }
</script>

<template>
  <div class="main">
    <p>count: {{ count }}</p>
    <button @click="changeCount">count</button>
  </div>
</template>
new: 10, old: 0
new: 20, old: 10
new: 30, old: 20
new: 40, old: 30
new: 50, old: 40
new: 90, old: 80

当监听的数据是 ref 定义的原始类型时,只要数据发生变化就会执行 watch 的回调函数。

  1. 使用 watch 监听 ref 定义的响应式数据(引用数据类型)
<script setup>
  import { ref, watch } from 'vue'

  let count = ref({ num: 0 })
  watch(count, () => {
    console.log(`count changed`)
  })
  const changeCount = () => {
    count.value.num += 10;
  }
</script>
<template>
  <div class="main">
    <p>count: {{ count }}</p>
    <button @click="changeCount">count</button>
  </div>
</template>

当点击“count”按钮时,界面上的count值更新,但控制台不打印任何输出。出现这种情况是因为 watch 没有对 count 进行深层监听。不过需要注意的是,此时 DOM 是能够更新的。

为了执行深度监视,只需要添加适当的参数 { deep: true }

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

  let count = ref({ num: 0 })
  watch(count, () => {
    console.log(`count changed`)
    },
    { deep: true }
  )
  const changeCount = () => {
    count.value.num += 10;
  }
</script>

<template>
  <div class="main">
    <p>count: {{ count }}</p>
    <button @click="changeCount">count</button>
  </div>
</template>

而当我们直接监听 count.value 而不使用deep: true时,结果与使用deep: true相同。

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

  let count = ref({ num: 0 })
  watch(count.value, () => {
    console.log(`count changed`)
  })
  const changeCount = () => {
    count.value.num += 10;
  }
</script>

<template>
  <div class="main">
    <p>count: {{ count }}</p>
    <button @click="changeCount">count</button>
  </div>
</template>

使用 watch 监听 reactive 定义的反应数据。

<script setup>
  import { reactive, watch } from 'vue'

  let count = reactive({ num: 0 })
  watch(count, () => {
    console.log(`count changed`)
  })
  const changeCount = () => {
    count.num += 10;
  }
</script>
<template>
  <div class="main">
    <p>count: {{ count }}</p>
    <button @click="changeCount">count</button>
  </div>
</template>

使用 watch 函数监听响应式数据时,无需添加 deep 属性来执行深层监听。

End.