在 Vue 开发中,最容易出问题的地方往往是对 Vue 响应式系统的误解。
响应式的核心不在于“数据一变,页面就刷新”,这是表象。真正的本质是数据和函数的关联。
当数据与函数关联后,数据的变化会触发相应函数的重新运行。这里要明确两个关键点:哪些数据和哪些函数关联了呢?
哪些数据会被关联?
1、函数中使用的数据:只有当函数用到了某个数据时,这个数据才与函数关联。例如,读取对象中的某个属性。
2、读取或使用的数据是响应式数据:只有 ref 或 reactive 包裹的数据才具备响应式特性,普通数据是不会触发关联函数运行的。
哪些函数会被监控?
被监控的函数,在 vue2 是放在 Wacther 内部的,在 vue3 是放在 effect 内部的。
1、render:渲染函数,也就解释了为什么数据一变,页面刷新,因为 render 是被监控的。
2、watchEffect:自动依赖追踪,数据变化时,函数会自动重新执行。
3、watch:显式地监听指定的数据变化,并执行相应回调。
4、computed:计算属性,它是基于响应式数据计算得来的,数据变化会重新计算值。
因此,只要满足这两点:响应式数据和被监控的函数,数据变化时,函数就会重新运行,页面表现才会随之更新。
举个 🌰 1
<template>
<div class="container">
<p>count:{{ count }}</p>
<p>double:{{ doubleCount }}</p>
<button @click="count++" class="btn">increase</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const doubleCount = ref(count.value * 2)
</script>
解释:
1、render 函数关联了 count 和 doubleCount 数据,count 是响应式数据,因此,count 变化,页面刷新。
2、为什么 doubleCount 没有变化?doubleCount 只是和 count 进行关联,但是响应式本质没有数据与数据的关联,是数据和函数的关联。因此 doubleCount 不会改变。
🌰 2
import { ref, watchEffect } from 'vue'
const count = ref(0)
const doubleCount = ref(0)
watchEffect(() => {
doubleCount.value = count.value * 2
})
解释:
1、现在被监控的函数有两个,render 和 watchEffect,在 watchEffect 中使用了 count,并且 count 是响应式数据,也就是说,render 函数关联两个数据,wacthEffect 关联一个数据。
2、当点击按钮改变 count 时,两个函数都会运行,wacthEffect 运行时,doubleCount 重新赋值,重新展示。
🌰 3
import { ref, watchEffect } from 'vue'
const count = ref(0)
const useDouble = (count) => {
const doubleCount = ref(count)
watchEffect(() => {
doubleCount.value = count * 2
})
return doubleCount
}
const doubleCount = useDouble(count.value)
解释:
1、为什么 doubleCount 没有变化?因为 useDouble 函数并没有被监控,只是一个普通的函数。
2、在 useDouble 函数中,有被监控的 wacthEffect 函数,但是函数内部没有读取某个属性,读取的 count 不是响应式数据,而是一个原始数据。
因此,count 变化后,render 运行,但是 watchEffect 不会执行。
🌰 4
import { computed, ref } from 'vue'
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
解释:
computed 被监控的函数关联了响应式数据 count,因此 count 变化后,render 函数和 computed 函数都会重新运行。
🌰 5
import { computed, ref } from 'vue'
const count = ref(0)
const useDouble = (count) => {
const doubleCount = computed(() => count * 2)
return doubleCount
}
const doubleCount = useDouble(count.value)
解释:
useDouble 是普通函数,没有被监控,内部被监控的 computed 没有关联响应式数据,也没有读取某个对象的某个属性。因此 count 变化时,computed 函数不会重新运行。
🌰 6
import { computed, ref } from 'vue'
const count = ref(0)
const useDouble = (count) => {
const doubleCount = computed(() => count.value * 2)
return doubleCount
}
const doubleCount = useDouble(count)
解释:
为什么这种写法可以改变 doubleCount 呢?因为 computed 函数读取了对象的属性,也就是我们传递的是响应式对象,而不是一个普通数据。实现了数据和函数直接的关联。
以上就是 Vue 响应式原理的本质,当我们遇到某些疑惑不能解决时,不妨分析一下其根源。