马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
在 Vue 3 中,reactive 和 ref 是实现响应式数据的两个核心 API,它们的设计目标和使用场景有所差别。以下是两者的详细对比:
1. 基本定义与核心功能
特性reactiveref作用创建对象范例的响应式署理(对象、数组、Map 等)创建一个响应式的引用,可用于基本范例或对象返回值原始对象的署理(Proxy)包含 value 属性的响应式对象(如 { value: xxx })响应式原理基于对象的属性劫持(Proxy + Reflect)基于 getter/setter 劫持,基本范例和对象统一处理解包机制无(直接访问属性)对象会主动解包为 reactive,基本范例需通过 .value 访问 2. 详细区别
(1) 实用数据范例
- reactive
仅用于对象范例(包罗普通对象、数组、Map、Set 等),无法直接处理基本范例(如 string、number、boolean)。
javascript
- // 正确:对象类型
- const state = reactive({ count: 0 });
- // 错误:不能直接处理基本类型(会被视为对象属性名)
- const num = reactive(1); // 报错:reactive() expects an object
复制代码 - ref
可处理所有范例(基本范例或对象)。对于对象,ref 内部会主动通过 reactive 转为响应式署理(即 “主动解包”)。
javascript
- // 基本类型
- const count = ref(0);
- // 对象类型(内部自动转为 reactive 代理)
- const obj = ref({ name: 'Vue' });
复制代码 (2) 访问方式
- reactive
直接通过属性访问,无需额外语法:
javascript
- const state = reactive({ count: 0 });
- state.count = 1; // 直接修改属性
复制代码 - ref
- 基本范例:需通过 .value 访问和修改: javascript
- const count = ref(0);
- count.value = 1; // 修改需通过 .value
复制代码 - 对象范例:由于主动解包,可直接访问属性(内部已转为 reactive 署理): javascript
- const obj = ref({ name: 'Vue' });
- obj.value.name = 'Vue 3'; // 直接修改属性(等价于 reactive 的操作)
- // 或通过解包后访问(模板中无需 .value)
复制代码
(3) 响应式解包规则
- 在模板中
- reactive 创建的对象:直接通过属性名访问,无需特殊处理。
- ref 创建的值:
- 基本范例:模板中会主动解包,直接使用 {{ count }} 即可(无需 {{ count.value }})。
- 对象范例:同样主动解包,等价于 reactive,直接访问属性(如 {{ obj.name }})。
- 在 JavaScript 中
- 若从 ref 中解构属性,需使用 toRefs 或手动保留 ref 引用,否则会失去响应式: javascript
- const obj = ref({ name: 'Vue', age: 3 });
- const { name } = obj.value; // 错误:name 是普通值,失去响应式
- const { name } = toRefs(obj.value); // 正确:通过 toRefs 保持响应式
复制代码
(4) 重新赋值与响应式
- reactive
署理对象指向固定,不能直接重新赋值为新对象(否则会失去响应式),需通过修改属性实现更新:
javascript
- const state = reactive({ count: 0 });
- state = { count: 1 }; // 错误:直接赋值会导致响应式丢失
- state.count = 1; // 正确:修改属性
复制代码 - ref
可以重新赋值为新值(基本范例或对象),响应式会主动更新:
javascript
- const count = ref(0);
- count.value = 1; // 基本类型重新赋值(正确)
- const obj = ref({ name: 'Vue' });
- obj.value = { name: 'React' }; // 对象重新赋值(正确,内部会重新创建 reactive 代理)
复制代码 (5) 组合式 API 中的使用
- reactive
适合定义包含多个属性的对象状态,常用于复杂状态管理:
javascript
- setup() {
- const state = reactive({
- count: 0,
- user: { name: 'Alice' }
- });
- return { state }; // 模板中通过 state.count 访问
- }
复制代码 - ref
适合定义单个值(基本范例或对象),或须要在函数间转达的独立状态,且返回时无需嵌套对象:
javascript
- setup() {
- const count = ref(0);
- const user = ref({ name: 'Alice' });
- return { count, user }; // 模板中直接使用 count 和 user.name
- }
复制代码 3. 典型使用场景
场景reactive 更合适ref 更合适定义对象 / 数组状态✅(reactive({ a: 1, b: 2 }))✅(ref({ a: 1, b: 2 }),主动解包)定义基本范例状态(如计数)❌(不支持)✅(ref(0))函数返回单个响应式值❌(需返回对象)✅(直接返回 ref(0),模板主动解包)解构响应式对象并保持响应式须要共同 toRefs(const { a, b } = toRefs(state))直接解构 ref(如 const { value } = count,但通常无需解构)动态创建响应式变量需手动构建对象直接使用 ref 包裹恣意值 4. 最佳实践
- 基本范例用 ref:
无论何时须要响应式的基本范例(如 count、isLoading),直接使用 ref。
javascript
- const count = ref(0);
- const isLoading = ref(false);
复制代码 - 对象 / 数组用 ref 或 reactive:
- 若状态是单个对象 / 数组,推荐用 ref(统一接口,主动解包): javascript
- const user = ref({ name: 'Vue', age: 3 });
复制代码 - 若须要在一个对象中整合多个状态(如表单数据),可用 reactive: javascript
- const form = reactive({
- name: 'Alice',
- email: 'alice@example.com'
- });
复制代码
- 解构时保持响应式:
当从 reactive 对象中解构属性时,必须使用 toRefs 制止失去响应式:
javascript
- const state = reactive({ a: 0, b: 0 });
- const { a, b } = toRefs(state); // a 和 b 是 ref,保持响应式
复制代码 - 制止混合使用导致杂乱:
- 尽量统一风格:若状态是对象,要么全用 reactive,要么全用 ref 包裹对象。
- 优先使用 ref 包裹对象:因为 ref 可以无缝处理基本范例和对象,且返回值更简便(无需嵌套对象)。
总结
- ref 是更通用的选择:可以处理所有数据范例,对象会主动解包为 reactive,且在组合式 API 中返回更方便(直接返回独立的 ref 变量)。
- reactive 适合复杂对象:当须要定义包含多个属性的对象状态,且不想通过 .value 访问时(固然 ref 包裹对象后也无需 .value,但 reactive 更直接)。
明白两者的核心区别后,可根据具体场景选择最合适的 API,制止响应式失效问题(如之前代码中 reactive 对象直接赋值导致的更新失败,改用 ref 或正确更新属性即可解决)。
====================================================================
当须要整体更新 reactive 对象中的某个嵌套对象(例如从接口获取全新的用户数据并覆盖原有对象),同时又要保持响应式时,关键是不替换整个 reactive 对象的引用,而是通过以下两种方式更新其属性:
方法一:使用 Object.assign 合并新对象(推荐)
实用场景:
- 已有 reactive 对象(如 state.user 是 reactive 创建的署理对象)。
- 须要用新对象的属性覆盖或补充原有对象的属性,而非完全替换整个对象的引用。
示例:
javascript
- import { reactive } from 'vue';
- // 初始化:state 是 reactive 对象,user 是嵌套对象
- const state = reactive({
- user: { name: 'Alice', age: 30 }
- });
- // 假设从接口获取新用户数据(普通对象)
- const newUser = { name: 'Bob', age: 35, city: 'Beijing' };
- // 正确写法:将新属性合并到现有 reactive 对象中(保持代理引用)
- Object.assign(state.user, newUser);
- // 等价于:state.user = { ...state.user, ...newUser }; (但此写法错误,见下方说明)
复制代码 关键原理:
- Object.assign(target, source) 会将 source 的属性直接复制到 target(即 state.user 署理对象),不改变 state.user 的引用,因此响应式得以保留。
- 错误写法:state.user = { ...state.user, ...newUser } 会创建一个新的普通对象并赋值给 state.user,导致其失去 reactive 署理,响应式失效。
方法二:重新赋值时保持 reactive 署理(实用于全新对象)
实用场景:
- 须要完全替换 reactive 对象中的某个嵌套对象(例如 state.user 本来不存在,或须要用全新的对象结构覆盖)。
- 确保新对象被重新包裹为 reactive 署理,或通过 ref 间接处理(更推荐)。
方式 1:对新对象重新应用 reactive(不推荐,可能导致性能问题)
javascript
- // 错误:直接赋值新普通对象(失去响应式)
- state.user = newUser;
- // 正确:对新对象创建 reactive 代理后再赋值(保持响应式)
- state.user = reactive(newUser);
- // 注意:此时 state.user 是新的代理对象,原有代理会被丢弃,可能导致不必要的依赖追踪
复制代码 方式 2:用 ref 包裹对象(更优雅的解决方案)
假如嵌套对象须要频繁整体更新,发起将其定义为 ref,而非直接作为 reactive 的属性:
javascript
- import { reactive, ref } from 'vue';
- // 初始化:用 ref 包裹 user 对象(可整体更新)
- const state = reactive({
- user: ref({ name: 'Alice', age: 30 }) // user 是 ref 对象
- });
- // 从接口获取新用户数据
- const newUser = { name: 'Bob', age: 35, city: 'Beijing' };
- // 正确写法:直接赋值给 ref 的 value(内部自动处理响应式)
- state.user.value = newUser;
- // 或先解包再赋值(模板中无需 .value,自动解包)
复制代码 原理:
- ref 答应重新赋值 value(包罗对象),且会主动对新对象创建 reactive 署理(即 “主动解包”),因此响应式得以保留。
- 相比直接使用 reactive 嵌套对象,ref 更机动,适合须要整体替换的场景。
方法三:解构新对象并逐个更新属性(实用于少量属性)
实用场景:
- 新对象与原对象结构差别较小,只需更新部门属性。
- 希望保持代码可读性,制止使用 Object.assign。
示例:
javascript
- // 假设 newUser 包含需要更新的属性
- state.user.name = newUser.name;
- state.user.age = newUser.age;
- state.user.city = newUser.city; // 直接更新每个属性,保持代理引用
复制代码 核心原则:制止丢失署理引用
无论接纳哪种方法,核心是确保 reactive 对象的引用不被替换:
- 对 reactive 嵌套对象:通过修改其属性或合并新属性(Object.assign),而非重新赋值整个对象。
- 对频繁整体更新的对象:改用 ref 包裹(const obj = ref(initialValue)),答应直接赋值新对象到 obj.value,利用 ref 的主动响应式处理。
完整示例对比(错误 vs 正确)
错误写法(失去响应式):
javascript
- const state = reactive({ user: { name: 'Alice' } });
- // 错误:直接赋值新对象,user 失去 reactive 代理
- state.user = { name: 'Bob' };
- // 视图不会更新,因为 state.user 现在是普通对象
复制代码 正确写法 1(合并属性):
javascript
- const state = reactive({ user: { name: 'Alice' } });
- const newUser = { name: 'Bob', age: 30 };
- // 正确:合并新属性到现有代理对象
- Object.assign(state.user, newUser);
- // 或逐个赋值:state.user.name = 'Bob'; state.user.age = 30;
- // 视图会正确更新
复制代码 正确写法 2(用 ref 包裹对象):
javascript
- const state = reactive({ user: ref({ name: 'Alice' }) });
- const newUser = { name: 'Bob', age: 30 };
- // 正确:赋值给 ref 的 value,内部自动处理响应式
- state.user.value = newUser;
- // 视图会正确更新
复制代码 总结:推荐方案
- 优先使用 ref 包裹须要整体更新的对象:
定义时用 ref,更新时直接赋值 obj.value = newObj,简便且制止响应式丢失问题。
javascript
- const user = ref({}); // 初始化为 ref 对象
- user.value = await fetchUser(); // 直接赋值新对象,保持响应式
复制代码
- 若必须使用 reactive 嵌套对象:
使用 Object.assign 合并新属性,或逐个更新属性,确保不替换署理对象的引用。
javascript
- Object.assign(state.user, newUser); // 合并属性,保持代理
复制代码
通过以上方法,既能实现整体对象的更新,又能保持 Vue 3 的响应式特性,制止因引用丢失导致的视图差别步问题。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |