在 Vue 3 中,对普通数据类型(如字符串、数字、布尔值等)实现响应式,其核心方法是使用 ref
函数。这是因为 ES6 的 Proxy
无法直接代理基本类型值,ref
通过一个巧妙的包装机制解决了这个问题。
特性 | ref (用于基本数据类型) |
reactive (用于对象和数组) |
---|---|---|
包装方式 | 创建一个包含 .value 属性的响应式引用对象 |
直接使用 Proxy 代理整个对象 |
数据访问 | 需要通过 .value 属性访问和修改内部值 |
直接访问和修改属性 |
适用场景 | 基本数据类型(string, number, boolean等) | 对象、数组等引用类型 |
💁 ref
的工作原理
ref
的实现思路是:如果值本身不能被代理,那就创建一个对象来包装它,然后代理这个对象。
-
创建包装对象 :当你调用
ref(0)
时,Vue 内部实际上会创建一个普通的 JavaScript 对象(通常称为 "引用对象" )来包装你的值:{ value: 0 }
。 -
施加响应式 :接着,Vue 会对这个包装对象使用
reactive
方法,即用Proxy
将其包裹,使其变为响应式。这样,所有对.value
属性的读取和写入操作都能被拦截。 -
依赖收集与触发更新:
-
读取 :当你在组件的模板或计算属性中访问
count.value
时,Proxy
的get
拦截器会触发,Vue 会记录下这个依赖关系(这个过程称为 track)。 -
修改 :当你修改
count.value++
时,Proxy
的set
拦截器会触发,Vue 会通知所有依赖于此值的地方进行更新(这个过程称为 trigger)。
-
💡 为什么需要 .value
?
你可能会问,为什么不能像操作对象属性那样直接操作 ref
变量?关键在于 JavaScript 中基本数据类型是按值传递的 。如果直接传递数字 0
,没有任何方法可以拦截到对这个数字本身的修改。而通过 .value
访问,实际上是在访问一个响应式对象的属性,这个属性可以被 Proxy
精确地拦截和追踪。
不过,Vue 编译器在 模板 中会自动对 ref
进行解包,所以你可以在模板中直接使用 {``{ count }}
而不需要写 {``{ count.value }}
。在 <script setup>
中编写的逻辑代码则仍需使用 .value
。
🛠️ 使用技巧与最佳实践
-
模板中自动解包 :在模板里,你可以直接使用
ref
变量名,Vue 会自动帮你解包。<template> <button @click="increment">{{ count }}</button> <!-- 无需 .value --> </template>
-
结合
reactive
使用 :当你在一个响应式对象内部访问ref
时,它也会被自动解包。const count = ref(0); const state = reactive({ count }); console.log(state.count); // 直接输出 0,无需 .value
-
使用
toRefs
保持响应式 :当使用reactive
定义的对象被解构或展开时,可以使用toRefs
将每个属性转换为ref
,以保持其响应性。
💎 总结
简单来说,Vue 3 通过 ref
函数将基本数据类型"装箱"成一个对象,然后利用 Proxy
代理这个包装对象,从而间接实现了对基本数据类型的响应式追踪。虽然这带来了在 JavaScript 中需要操作 .value
的一点额外成本,但模板中的自动解包让开发体验保持了流畅。