Vue3 的 ref 与 reactive 区别与实现原理
字数 2093 2025-11-04 08:34:40
Vue3 的 ref 与 reactive 区别与实现原理
题目描述
Vue3 的响应式系统提供了 ref 和 reactive 两个核心 API 来创建响应式数据。理解它们的区别、适用场景和底层实现原理,对于正确使用 Vue3 和应对面试至关重要。
知识讲解
第一步:设计初衷与基本用法
-
reactive
- 设计目标:用于创建一个深度响应式的对象或数组。它通过 Proxy 代理整个对象,递归地将对象的所有属性都转换为响应式的。
- 基本用法:
import { reactive } from 'vue'; const state = reactive({ count: 0, user: { name: 'Alice' } }); // 修改属性,视图会自动更新 state.count++; state.user.name = 'Bob'; // 嵌套属性也是响应式的
-
ref
- 设计目标:用于创建一个可以包装任意值(包括基本类型,如 string, number, boolean,以及对象和数组)的响应式引用。它解决的是 reactive 无法直接对基本类型进行代理的问题。
- 基本用法:
import { ref } from 'vue'; const count = ref(0); // 包装一个数字 const message = ref('Hello'); // 包装一个字符串 const obj = ref({ name: 'Alice' }); // 包装一个对象 // 访问和修改需要通过 .value 属性 console.log(count.value); // 0 count.value++; // 修改 message.value = 'World'; obj.value.name = 'Bob'; // 注意,这里访问的是 .value 上的属性 - 模板中的自动解包:在模板中,你不需要写
.value,Vue 会自动帮你解包。<template> <div>{{ count }}</div> <!-- 无需写成 count.value --> </template>
第二步:核心区别对比
| 特性 | ref |
reactive |
|---|---|---|
| 数据类型 | 可以包装任何值(基本类型和引用类型) | 只能用于对象类型(Object, Array, Map, Set) |
| 访问/修改 | 需要通过 .value 属性 |
可以直接访问和修改属性 |
| 模板使用 | 自动解包,无需 .value |
直接访问属性 |
| TS 类型定义 | Ref<T> |
对象本身就是响应式的,类型更直观 |
| 实现原理 | 通过一个对象的 .value 属性的 getter/setter 实现 |
通过 Proxy 代理整个对象 |
第三步:底层实现原理剖析
-
reactive 的实现核心
- 技术:基于 ES6 的
Proxy。 - 过程:
reactive(obj)会返回obj的 Proxy 代理。- Getter 追踪:当你读取代理对象的某个属性(如
state.count)时,Proxy 的get陷阱函数会触发。Vue 会在这里执行track(target, key),将当前正在运行的副作用函数(例如,组件的渲染函数)"收集"起来,建立依赖关系。这叫做依赖收集。 - Setter 触发更新:当你修改代理对象的属性(如
state.count = 1)时,Proxy 的set陷阱函数会触发。Vue 会在这里执行trigger(target, key),去找到所有依赖于这个属性的副作用函数,并重新执行它们。这叫做触发更新。
- 递归响应:在
get陷阱中,如果读取到的属性值本身也是一个对象,Vue 会递归地对其调用reactive(),确保嵌套属性也是响应式的。
- 技术:基于 ES6 的
-
ref 的实现核心
- 技术:基于普通对象的属性访问器(getter/setter)。
- 过程:
ref(value)会返回一个被称为 "ref 对象" 的普通对象,其结构为{ value: ... }。- 这个 ref 对象有一个私有的属性
_value用来存储实际的值。 - 它对外暴露了一个公共的
value属性。对这个value属性的读取和修改,被 getter 和 setter 拦截。 - Getter 追踪:当你读取
myRef.value时,getter 函数被调用。它内部会执行track(ref, 'value'),追踪对'value'这个 key 的访问。 - Setter 触发更新:当你设置
myRef.value = newValue时,setter 函数被调用。它内部会先更新_value,然后执行trigger(ref, 'value'),通知所有依赖进行更新。
- ref 的优化:如果传入
ref的是一个对象,Vue 内部会自动调用reactive方法来深度处理这个对象。所以ref({})在响应性方面等价于reactive({}),只是访问方式不同(需要通过.value)。
第四步:如何选择与总结
-
使用
ref的场景:- 定义基本类型的响应式数据(如字符串、数字、布尔值)。
- 定义未来值不确定的响应式数据。因为
ref更通用。 - 在逻辑复用(Composables)中返回响应式数据时,
ref更容易被解构而保持响应性。
-
使用
reactive的场景:- 定义一组逻辑上相关联的数据,比如一个表单对象、一个页面的状态管理对象。这样你不需要在每个属性前加
.value,代码更简洁。
- 定义一组逻辑上相关联的数据,比如一个表单对象、一个页面的状态管理对象。这样你不需要在每个属性前加
-
核心记忆点:
reactive的局限性是只对对象类型有效,且使用 Proxy。ref的普适性是因为它用 getter/setter 包装了.value属性,从而可以容纳任何值。- 它们最终都汇入了 Vue3 统一的响应式系统(
track和trigger)。