初学vue的时候,常常苦恼于什么时候该用响应式状态,什么时候用普通变量。学习过程中逐渐对响应式状态这一概念有了更深的感悟,在此记下笔记。这篇博客只涉及对响应式的直观理解,不会花太多篇幅细究实现原理。
我是直接从vue3开始学习的,起初,我以为在setup中声明的响应式状态能够追踪所有引用自己的变量并及时通知这些依赖变量更新。但随之我发现,直接在js中用响应式给变量赋值并不能实现响应式的作用,只有在<template>中使用的响应式状态能够按期望执行。
<script setup>
import {ref , reactive} from 'vue'
const refObj = ref('Monday')
const obj = refObj.value
const change = () => {
refObj.value = 'Tuesday'
console.log('obj = ', obj)
}
</script>
<template>
<text>{{refObj}}</text>
<button @click = 'change'>change</button>
</template>
像在上面这段代码中,声明了一个响应式状态refObj,并用这个响应式状态的值给普通变量obj赋值,在template中,将text的内容设置为refObj。点击button后,text的文本内容变为’Tuesday’,控制台打印obj的值仍然是’Monday’。
一个响应式状态,并不是其他变量使用它赋值后,就可以自动追踪这个响应式状态来更新自己的值。在实践中,我逐渐感觉到,响应式状态,是“有能力通知自己的变化的对象”。在一些讲解vue响应式原理的课程中,可以看到想要真正利用响应式状态建立数据之间的依赖关系,是通过一个effect()函数,和一个内部使用了响应式状态的副作用函数实现的。
举个例子,我们想要建立一个路程=时间X速度的数据依赖关系,首先,我们声明了两个响应式状态分别对应时间和速度
const speed = ref(10);//初速度设定为10m/s
const time = ref(100);//初时间设定为100s
现在,我们的响应式状态speed和time是具有“通知依赖自己的数据更新”这一能力的对象。然后,为了建立这种依赖关系,我们需调用一次effect()
let distance;
effect(()=>{
distance = speed.value*time.value;
});
至此,成功建立了数据依赖关系,当speed或者time发生变化时,effect()接受的副作用函数会执行一次,更新distance的值。
但是现在出现了新的需求,引入常数totalDistance和restDistance,要求建立restDistance = totalDistance - distance的的依赖关系。restDistance的值依赖distance的值,但是distance是一个普通变量,并不是响应式对象,不具有通知依赖自己的数据更新的能力。形象地说,我们要将上图的依赖关系改成下面这样:
这就是computed()的作用,一个简单的computed可以写成这样
const computed = (func)={
const refObj = ref();
refObj.value = effect(()=>{refObj.value = func()});
}
也就是说,computed接受一个函数,声明一个响应式状态,响应式状态的值依赖于接收的函数参数,最终返回这个响应式状态。通过computed,省去了手动对effect的调用,最终返回一个具有目标数据依赖关系的响应式状态。依赖于响应式状态的变量本身可以不是响应式状态,而computed()返回的往往是依赖于响应式状态,且本身也是响应式状态的对象,也就是说,这一返回对象可以接着作为其他变量所依赖的数据对象。
在模板中,引用响应式状态给dom元素属性赋值,相当于直接在js中通过effect将dom元素的属性与响应式状态绑定。而在js中,想要建立这样的数据依赖关系,更多时候是直接使用computed建立数据之间的依赖关系。