Vue 3 reactive 和 ref 区别及 失去响应性问题

打印 上一主题 下一主题

主题 1640|帖子 1640|积分 4920

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

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
    1. // 正确:对象类型
    2. const state = reactive({ count: 0 });
    3. // 错误:不能直接处理基本类型(会被视为对象属性名)
    4. const num = reactive(1); // 报错:reactive() expects an object
    复制代码
  • ref
    可处理所有范例(基本范例或对象)。对于对象,ref 内部会主动通过 reactive 转为响应式署理(即 “主动解包”)。
    javascript
    1. // 基本类型
    2. const count = ref(0);
    3. // 对象类型(内部自动转为 reactive 代理)
    4. const obj = ref({ name: 'Vue' });
    复制代码
(2) 访问方式



  • reactive
    直接通过属性访问,无需额外语法:
    javascript
    1. const state = reactive({ count: 0 });
    2. state.count = 1; // 直接修改属性
    复制代码
  • ref

    • 基本范例:需通过 .value 访问和修改: javascript
      1. const count = ref(0);
      2. count.value = 1; // 修改需通过 .value
      复制代码
    • 对象范例:由于主动解包,可直接访问属性(内部已转为 reactive 署理): javascript
      1. const obj = ref({ name: 'Vue' });
      2. obj.value.name = 'Vue 3'; // 直接修改属性(等价于 reactive 的操作)
      3. // 或通过解包后访问(模板中无需 .value)
      复制代码

(3) 响应式解包规则



  • 在模板中

    • reactive 创建的对象:直接通过属性名访问,无需特殊处理。
    • ref 创建的值:

      • 基本范例:模板中会主动解包,直接使用 {{ count }} 即可(无需 {{ count.value }})。
      • 对象范例:同样主动解包,等价于 reactive,直接访问属性(如 {{ obj.name }})。


  • 在 JavaScript 中

    • 若从 ref 中解构属性,需使用 toRefs 或手动保留 ref 引用,否则会失去响应式: javascript
      1. const obj = ref({ name: 'Vue', age: 3 });
      2. const { name } = obj.value; // 错误:name 是普通值,失去响应式
      3. const { name } = toRefs(obj.value); // 正确:通过 toRefs 保持响应式
      复制代码

(4) 重新赋值与响应式



  • reactive
    署理对象指向固定,不能直接重新赋值为新对象(否则会失去响应式),需通过修改属性实现更新:
    javascript
    1. const state = reactive({ count: 0 });
    2. state = { count: 1 }; // 错误:直接赋值会导致响应式丢失
    3. state.count = 1; // 正确:修改属性
    复制代码
  • ref
    可以重新赋值为新值(基本范例或对象),响应式会主动更新:
    javascript
    1. const count = ref(0);
    2. count.value = 1; // 基本类型重新赋值(正确)
    3. const obj = ref({ name: 'Vue' });
    4. obj.value = { name: 'React' }; // 对象重新赋值(正确,内部会重新创建 reactive 代理)
    复制代码
(5) 组合式 API 中的使用



  • reactive
    适合定义包含多个属性的对象状态,常用于复杂状态管理:
    javascript
    1. setup() {
    2.   const state = reactive({
    3.     count: 0,
    4.     user: { name: 'Alice' }
    5.   });
    6.   return { state }; // 模板中通过 state.count 访问
    7. }
    复制代码
  • ref
    适合定义单个值(基本范例或对象),或须要在函数间转达的独立状态,且返回时无需嵌套对象:
    javascript
    1. setup() {
    2.   const count = ref(0);
    3.   const user = ref({ name: 'Alice' });
    4.   return { count, user }; // 模板中直接使用 count 和 user.name
    5. }
    复制代码
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
    1. const count = ref(0);
    2. const isLoading = ref(false);
    复制代码
  • 对象 / 数组用 ref 或 reactive

    • 若状态是单个对象 / 数组,推荐用 ref(统一接口,主动解包): javascript
      1. const user = ref({ name: 'Vue', age: 3 });
      复制代码
    • 若须要在一个对象中整合多个状态(如表单数据),可用 reactive: javascript
      1. const form = reactive({
      2.   name: 'Alice',
      3.   email: 'alice@example.com'
      4. });
      复制代码

  • 解构时保持响应式
    当从 reactive 对象中解构属性时,必须使用 toRefs 制止失去响应式:
    javascript
    1. const state = reactive({ a: 0, b: 0 });
    2. 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
  1. import { reactive } from 'vue';
  2. // 初始化:state 是 reactive 对象,user 是嵌套对象
  3. const state = reactive({
  4.   user: { name: 'Alice', age: 30 }
  5. });
  6. // 假设从接口获取新用户数据(普通对象)
  7. const newUser = { name: 'Bob', age: 35, city: 'Beijing' };
  8. // 正确写法:将新属性合并到现有 reactive 对象中(保持代理引用)
  9. Object.assign(state.user, newUser);
  10. // 等价于: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
  1. // 错误:直接赋值新普通对象(失去响应式)
  2. state.user = newUser;
  3. // 正确:对新对象创建 reactive 代理后再赋值(保持响应式)
  4. state.user = reactive(newUser);
  5. // 注意:此时 state.user 是新的代理对象,原有代理会被丢弃,可能导致不必要的依赖追踪
复制代码
方式 2:用 ref 包裹对象(更优雅的解决方案)

假如嵌套对象须要频繁整体更新,发起将其定义为 ref,而非直接作为 reactive 的属性:

javascript
  1. import { reactive, ref } from 'vue';
  2. // 初始化:用 ref 包裹 user 对象(可整体更新)
  3. const state = reactive({
  4.   user: ref({ name: 'Alice', age: 30 }) // user 是 ref 对象
  5. });
  6. // 从接口获取新用户数据
  7. const newUser = { name: 'Bob', age: 35, city: 'Beijing' };
  8. // 正确写法:直接赋值给 ref 的 value(内部自动处理响应式)
  9. state.user.value = newUser;
  10. // 或先解包再赋值(模板中无需 .value,自动解包)
复制代码
原理:



  • ref 答应重新赋值 value(包罗对象),且会主动对新对象创建 reactive 署理(即 “主动解包”),因此响应式得以保留。
  • 相比直接使用 reactive 嵌套对象,ref 更机动,适合须要整体替换的场景。
方法三:解构新对象并逐个更新属性(实用于少量属性)

实用场景:



  • 新对象与原对象结构差别较小,只需更新部门属性。
  • 希望保持代码可读性,制止使用 Object.assign。
示例:

javascript
  1. // 假设 newUser 包含需要更新的属性
  2. state.user.name = newUser.name;
  3. state.user.age = newUser.age;
  4. state.user.city = newUser.city; // 直接更新每个属性,保持代理引用
复制代码
核心原则:制止丢失署理引用

无论接纳哪种方法,核心是确保 reactive 对象的引用不被替换


  • 对 reactive 嵌套对象:通过修改其属性或合并新属性(Object.assign),而非重新赋值整个对象。
  • 对频繁整体更新的对象:改用 ref 包裹(const obj = ref(initialValue)),答应直接赋值新对象到 obj.value,利用 ref 的主动响应式处理。
完整示例对比(错误 vs 正确)

错误写法(失去响应式):

javascript
  1. const state = reactive({ user: { name: 'Alice' } });
  2. // 错误:直接赋值新对象,user 失去 reactive 代理
  3. state.user = { name: 'Bob' };
  4. // 视图不会更新,因为 state.user 现在是普通对象
复制代码
正确写法 1(合并属性):

javascript
  1. const state = reactive({ user: { name: 'Alice' } });
  2. const newUser = { name: 'Bob', age: 30 };
  3. // 正确:合并新属性到现有代理对象
  4. Object.assign(state.user, newUser);
  5. // 或逐个赋值:state.user.name = 'Bob'; state.user.age = 30;
  6. // 视图会正确更新
复制代码
正确写法 2(用 ref 包裹对象):

javascript
  1. const state = reactive({ user: ref({ name: 'Alice' }) });
  2. const newUser = { name: 'Bob', age: 30 };
  3. // 正确:赋值给 ref 的 value,内部自动处理响应式
  4. state.user.value = newUser;
  5. // 视图会正确更新
复制代码
总结:推荐方案


  • 优先使用 ref 包裹须要整体更新的对象
    定义时用 ref,更新时直接赋值 obj.value = newObj,简便且制止响应式丢失问题。
    javascript
    1. const user = ref({}); // 初始化为 ref 对象
    2. user.value = await fetchUser(); // 直接赋值新对象,保持响应式
    复制代码

  • 若必须使用 reactive 嵌套对象
    使用 Object.assign 合并新属性,或逐个更新属性,确保不替换署理对象的引用。
    javascript
    1. Object.assign(state.user, newUser); // 合并属性,保持代理
    复制代码


通过以上方法,既能实现整体对象的更新,又能保持 Vue 3 的响应式特性,制止因引用丢失导致的视图差别步问题。



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

滴水恩情

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表