1. 响应式系统
简单来说,响应式就是当数据发生变化时,依赖这个数据的地方自动更新,而不需要开发者手动操作 DOM。这在前端的开发中,尤其重要,在一个页面上多处依赖一个变量的情况下,当变量发生变化,需要同时维护多处的 DOM 更新。以下面一个简单的代码为例:
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
<div class="input">
a: <input type="text" id="input_a" value="10">
</div>
<div class="input">
b: <input type="text" id="input_b" value="10">
</div>
<div class="output">
a+b: <input type="text" id="output" disabled>
</div>
<button id="button">Add</button>
<script>
function add() {
let a = document.getElementById("input_a").value;
let b = document.getElementById("input_b").value;
let c = Number(a) + Number(b);
document.getElementById("output").value = c;
}
document.getElementById("button").addEventListener("click", add);
</script>
</body>
</html>
上面的例子中,监听了 button 的 click 事件,当发生 click 时,取出 input_a 和 input_b 中的内容,并相加更新到 output 中,当前只有一处 output,如果存在多处的 output,那么就得维护更新多个 document.getElementById("output").value 的操作。响应式系统正是针对这样的问题设计的。
2. ref 和 reactive
Vue 3提供了两个主要的函数来创建响应式数据:ref 和 reactive。
2.1. ref 函数
响应式 ref 函数创建一个响应式引用。在模板中,你可以直接使用响应式引用的值,而在 JavaScript 代码中,你需要通过 .value 属性来访问或修改它的值。
<script setup lang="ts">
import { ref } from "vue";
const sum = ref<number>(0);
const a = ref<number>(10);
const b = ref<number>(10);
const add = () => {
sum.value = a.value + b.value;
}
</script>
<template>
<div class="input">
a: <input type="text" id="input_a" v-model.number="a">
</div>
<div class="input">
b: <input type="text" id="input_b" v-model.number="b">
</div>
<div class="output">
a+b: {{ sum }}
</div>
<button id="button" @click="add">Add</button>
</template>
其中,{{ }} 称为插值,在 template 中会对 ref 进行自动解包,因此不需要再写成 {{ sum.value }}。
2.2. reactive 函数
响应式 reactive 函数创建一个响应式对象。你可以直接访问和修改它的属性。
<script setup lang="ts">
import { reactive } from "vue";
const data = reactive({
a: 10,
b: 10,
sum: 20
});
const add = () => {
data.sum = data.a + data.b;
}
</script>
<template>
<div class="input">
a: <input type="text" id="input_a" v-model.number="data.a">
</div>
<div class="input">
b: <input type="text" id="input_b" v-model.number="data.b">
</div>
<div class="output">
a+b: {{ data.sum }}
</div>
<button id="button" @click="add">Add</button>
</template>
2.3. 实现机制区别
响应式 ref 和 reactive 的区别主要体现在:
ref是为了让基本类型(如number,string)可以变为响应式,而reactive是为了让对象变为响应式ref创建的响应式数据需要通过.value属性进行访问和修改,而reactive创建的响应式对象可以直接访问和修改其属性。因此,ref更适合于处理基本类型,而reactive更适合于处理对象。
3. 底层原理分析
上述 ref 和 reactive 都能实现数据的依赖收集、派发更新等一系列的操作,这一切都是在类的基础上完成的,但两者之间的差异也正是因为类的构造不同造成的。
绝大部分的文章都会介绍到 Vue3 的响应式的核心是 Proxy,它能拦截对一个对象的读写操作,但是无法代理 number,string 这类基本类型,因此,也就出现了 ref 和 reactive 的差别:
reactive:reactive接收一个对象,并返回这个对象的Proxy代理。因此,你可以直接通过state.count这样的属性访问方式读写数据,Proxy会拦截这些操作并触发响应式更新。ref:为了能让基本类型也具备响应式,ref创建了一个包装对象refImpl。这个对象内部只有一个value属性,用来存放你真正的数据。ref正是对这个value属性进行了响应式处理,因此,要读取或修改里面的数据,就需要通过.value来操作。
4. 总结
响应式系统是 Vue 的基础,正式响应式的设计使得数据的依赖收集、派发更新变得简单,同时,在 Vue3 中,提供了 ref 和 reactive 两种响应式的支持,不同点在于 ref 是为了让基本类型(如 number,string)可以变为响应式,而 reactive 是为了让对象变为响应式,其本质是背后的实现方式的差异,这也就导致在使用上会存在差异。