浅谈Vue3中的reactive跟ref的区别以及源码实现
Vue 3 中 ref
与 reactive
的深度剖析与区别
Vue 3 引入了全新的 Composition API,极大地提升了开发者的编程体验与代码组织能力。其中,ref
与 reactive
是构建响应式数据模型的两大核心工具,它们分别服务于不同的场景并具有独特的使用方式与底层实现机制。本文将深入探讨 ref
与 reactive
的功能特点、适用范围、使用方式及底层原理,以便帮助开发者更好地理解和应用这两个关键概念。
一、基本概念与应用场景
1. ref
定义与用途:ref
是 Vue 3 提供的一种用于封装基本数据类型(如字符串、数字、布尔值)或单一引用类型值(如单个对象或数组)的函数。它返回一个带有 .value
属性的响应式对象,通过 .value
可以访问和更新封装的原始值。
应用场景:
- 存储和追踪独立的基本数据类型变量。
- 管理不需要深度响应式的单一对象或数组。
- 与模板交互,尤其是在模板中需要直接绑定到 DOM 属性或指令时。
2. reactive
定义与用途:reactive
用于创建深度响应式的对象,它可以处理包含多个属性(包括嵌套属性)的对象或数组。通过 reactive
包装的对象,其所有属性都会成为响应式的,并且能够自动追踪内部属性的增删改查。
应用场景:
- 构建复杂的对象模型,如用户信息、列表数据等。
- 需要深度监控对象结构变化,确保任何层级的属性变更都能触发视图更新。
- 与模板或计算属性等其他响应式依赖紧密协作,形成数据驱动的 UI 更新。
二、使用方式对比
1. ref
的使用
声明与访问:
import { ref } from 'vue';
const count = ref(0); // 声明一个响应式整数
console.log(count.value); // 输出: 0
count.value = 5; // 更新值
console.log(count.value); // 输出: 5
在模板中使用:
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="count.value++">Increment</button>
</div>
</template>
2. reactive
的使用
声明与访问:
import { reactive } from 'vue';
const user = reactive({
name: 'John Doe',
age: 30,
address: {
street: '123 Main St',
city: 'Anytown'
}
});
console.log(user.name); // 输出: 'John Doe'
user.age = 31; // 更新属性
console.log(user.age); // 输出: 31
在模板中使用:
<template>
<div>
<p>Name: {{ user.name }}</p>
<p>Age: {{ user.age }}</p>
<p>Address: {{ user.address.street }}, {{ user.address.city }}</p>
</div>
</template>
三、底层实现与响应式机制
1. ref
的实现与更新机制
ref
在底层通常通过一个简单的对象来封装原始值,并暴露 .value
属性。当 .value
被赋新值时,Vue 会识别到这个变化并触发相关的依赖更新。由于 ref
本身仅封装一个值,其更新仅涉及单个属性,因此更新成本较低,适用于不需要深度响应式的情况。
2. reactive
的实现与更新机制
reactive
则利用 JavaScript 的 Proxy
对象来创建一个透明的代理,对原对象的所有属性进行拦截。当访问或修改代理对象的属性时,Proxy
会触发相应的 get
或 set
钩子函数,这些钩子负责维护依赖关系和触发更新。由于 reactive
能够追踪对象任意层级属性的变化,因此适用于需要深度响应式的复杂数据结构。
四、手写ref跟reactive
手写 ref
函数
function createRef(value) {
const internalValue = value;
function getRef() {
trackRef(getRef);
return internalValue;
}
function setRef(newVal) {
if (internalValue !== newVal) {
internalValue = newVal;
triggerRef(getRef);
}
}
return {
get value() {
return getRef();
},
set value(newValue) {
setRef(newValue);
},
};
}
// 假设存在以下辅助函数,用于模拟依赖收集与更新触发
function trackRef(fn) {
// 收集依赖逻辑(此处省略具体实现)
}
function triggerRef(fn) {
// 触发依赖更新逻辑(此处省略具体实现)
}
在这个简化版的 ref
实现中,我们创建了一个闭包,内部存储了实际值 internalValue
。getRef
和 setRef
函数分别用于获取和设置值,并在此过程中模拟了依赖收集与更新触发。对外暴露的 value
属性作为访问器属性,其 get
和 set
方法分别调用 getRef
和 setRef
函数,实现了响应式行为。
手写 reactive
函数
function createReactiveObject(target) {
const handler = {
get(target, key) {
track(target, key);
return Reflect.get(target, key);
},
set(target, key, value) {
const oldValue = target[key];
const hadKey = hasOwn(target, key);
const result = Reflect.set(target, key, value);
if (!hadKey) {
trigger(target, "add", key, value);
} else if (hasChanged(value, oldValue)) {
trigger(target, "set", key, value, oldValue);
}
return result;
},
// ... 其他 Proxy 操作符的处理(此处省略)
deleteProperty(target, key) {
const hadKey = hasOwn(target, key);
const result = Reflect.deleteProperty(target, key);
if (hadKey) {
trigger(target, "delete", key);
}
return result;
},
};
return new Proxy(target, handler);
}
// 假设存在以下辅助函数,用于模拟依赖收集与更新触发
function track(target, key) {
// 收集依赖逻辑(此处省略具体实现)
}
function trigger(target, type, key, newValue, oldValue) {
// 触发依赖更新逻辑(此处省略具体实现)
}
function hasOwn(target, key) {
return Object.prototype.hasOwnProperty.call(target, key);
}
function hasChanged(value, oldValue) {
return value !== oldValue;
}
在这个简化版的 reactive
实现中,我们利用 Proxy
对目标对象进行代理,定义了 get
、set
、deleteProperty
等操作符的处理器。当访问或修改代理对象的属性时,会触发相应的处理器函数,进行依赖收集和更新触发。这里同样省略了具体的依赖收集与更新触发逻辑,但保留了对依赖类型(如“add”、“set”、“delete”)和新旧值的区分,以模拟真实情况下的精细更新。
五、总结与选择策略
ref
与 reactive
的主要区别在于:
- 数据类型与封装层次:
ref
适用于封装基本数据类型和单一引用类型值,而reactive
适用于构建深度响应式的对象或数组结构。 - 访问与修改方式:
ref
的值通过.value
属性访问和修改;reactive
的属性则直接访问和修改。 - 模板交互:在模板中,
ref
需要使用.value
(如{{ count.value }}
),而reactive
直接使用属性名(如{{ user.name }}
)。 - 底层实现与更新机制:
ref
通过简单对象封装,更新仅涉及单个属性;reactive
利用Proxy
实现深度响应式,能追踪对象内部任意属性的变化。
在实际开发中,选择使用 ref
还是 reactive
应基于数据结构的复杂性、响应式需求的深度以及与模板交互的便利性。对于独立的基本数据类型或单一对象/数组,优先考虑使用 ref
;而对于包含多个属性且需要深度响应式的复杂数据模型,则应选用 reactive
。理解并恰当运用这两者,是充分发挥 Vue 3 Composition API 效能的关键所在。
转载自:https://juejin.cn/post/7353544284418048052