- Published on
理解reactive、isReactive、shallowReactive
- Authors
- Name
- Deng Hua
Vue 3 中的响应式系统是其数据双向绑定的核心,它允许在响应式状态发生变化时自动更新 DOM。除了 ref 之外,还有 reactive 方法可用于创建响应式对象。
reactive 的作用是将普通对象转换为响应式对象。它递归地将对象的所有属性转换为响应式数据。它返回的是一个Proxy对象。
目录
reactive
reactive 的参数只能是对象、数组或集合类型(例如 Map 和 Set)。当对象内的属性在更改时将自动触发视图更新。
用法:
<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 的区别:
- 使用 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 的回调函数。
- 使用 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.