Vue3 中响应式

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提供了两个主要的函数来创建响应式数据:refreactive

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. 实现机制区别

响应式 refreactive 的区别主要体现在:

  • ref 是为了让基本类型(如 numberstring)可以变为响应式,而 reactive 是为了让对象变为响应式
  • ref 创建的响应式数据需要通过 .value 属性进行访问和修改,而 reactive 创建的响应式对象可以直接访问和修改其属性。因此,ref 更适合于处理基本类型,而 reactive 更适合于处理对象。

3. 底层原理分析

上述 refreactive 都能实现数据的依赖收集、派发更新等一系列的操作,这一切都是在类的基础上完成的,但两者之间的差异也正是因为类的构造不同造成的。

绝大部分的文章都会介绍到 Vue3 的响应式的核心是 Proxy,它能拦截对一个对象的读写操作,但是无法代理 numberstring 这类基本类型,因此,也就出现了 refreactive 的差别:

  • reactivereactive 接收一个对象,并返回这个对象的 Proxy 代理。因此,你可以直接通过 state.count 这样的属性访问方式读写数据,Proxy 会拦截这些操作并触发响应式更新。
  • ref:为了能让基本类型也具备响应式,ref 创建了一个包装对象 refImpl。这个对象内部只有一个 value 属性,用来存放你真正的数据。ref 正是对这个 value 属性进行了响应式处理,因此,要读取或修改里面的数据,就需要通过 .value 来操作。

4. 总结

响应式系统是 Vue 的基础,正式响应式的设计使得数据的依赖收集、派发更新变得简单,同时,在 Vue3 中,提供了 refreactive 两种响应式的支持,不同点在于 ref 是为了让基本类型(如 numberstring)可以变为响应式,而 reactive 是为了让对象变为响应式,其本质是背后的实现方式的差异,这也就导致在使用上会存在差异。