一文搞懂Vue3中的ref和reactive

 更新时间:2025年06月27日 16:07:50   作者:小林猿~  
Vue3中ref用于独立值或整体替换,reactive用于对象/数组的深度响应,访问方式不同,解构需用toRefs保持响应,团队应统一规范,按需选用,本文给大家介绍Vue3中的ref和reactive,感兴趣的朋友一起看看吧

引言

在 Vue3 中,响应式最常用的两个 API 就是 refreactive。很多开发者一开始对它们的区别不够明确,看到任何状态就想用 ref,或者对对象也习惯性用 ref 包一层,导致代码可读性、维护性下降,或者出现解构导致响应丢失、整体替换麻烦等问题。

1. 基本概念

  • ref:用于包装一个独立的响应式值。创建后会返回一个包含 .value 的对象,内部对 .value 做响应式跟踪。模板中 Vue 会自动解包 .value,在 JS 逻辑里需显式用 .value 访问或修改。

    import { ref } from 'vue';
    const count = ref(0);
    count.value++; // 触发响应
  • reactive:用于把一个对象或数组变为响应式 Proxy。返回的就是该对象的代理,访问写法如 state.prop,对内部嵌套对象/数组会递归转为响应式。

    import { reactive } from 'vue';
    const state = reactive({ a: 1, b: { c: 2 } });
    state.a = 3;          // 触发响应
    state.b.c = 5;        // 嵌套也响应

关键区别

  • ref 更适合包装原始类型或需要整体替换的场景;.value 代表实际值。
  • reactive 适合包装多字段对象/数组,直接访问属性更简洁。

2. 访问与解包:模板 vs JS 逻辑

  • 模板中:Vue 会自动对 ref 进行解包。例如:

    <template>
      <div>{{ count }}</div>         <!-- 如果 count = ref(0),模板会显示 0 -->
      <div>{{ state.a }}</div>       <!-- state = reactive({ a: ... }) -->
    </template>

    模板里使用 refreactive 返回的变量都可直接写,不用加 .value

  • JS 逻辑里

    • ref:必须用 .value 访问/赋值。
    • reactive:直接用 state.propstate.prop = newValue

示例:

import { ref, reactive } from 'vue';
export default {
  setup() {
    const loading = ref(false);
    const filters = reactive({ category: '', minPrice: null, maxPrice: null });
    function doSomething() {
      loading.value = true;           // ref
      filters.category = 'electronics'; // reactive
    }
    return { loading, filters, doSomething };
  }
};

3. 场景对比:何时用 ref,何时用 reactive

下面以电商常见需求为例,了解它们的使用差异。

3.1 单值状态 vs 多字段状态

  • 单值、标志位、计数器、页码、布尔 loading、是否收藏 等,典型用 ref。语义上就是一个变量,访问/修改都集中在 .value,且若需要整体替换该值(如重置、切换)也很方便。

    const page = ref(1);
    const loading = ref(false);
    const isFavorite = ref(false);
    // 切换时: page.value = 1; isFavorite.value = true/false
  • 多个相关字段聚合成一个对象,如搜索筛选条件、表单数据、购物车项集合和详情对象等,用 reactive 更直观:

    const filters = reactive({
      keyword: '',
      category: '',
      price: { min: null, max: null }
    });
    // 修改时: filters.keyword = 'xxx'; filters.price.min = 10

    若拆成多个 ref:const keyword = ref(''), const category = ref(''), …,当字段较多时不易管理;也无法一次性传递或传入 API。

3.2 整体替换 vs 逐字段更新

  • 需要整体替换状态对象:如“加载远程购物车数据并直接赋予当前状态”“一键清空并恢复初始对象”等。用 ref 包对象更简单:

    const cartRef = ref({ items: [], couponCode: '', total: 0 });
    // 加载后整体替换
    cartRef.value = newCartObj;

    若用 reactive

    const cart = reactive({ items: [], couponCode: '', total: 0 });
// 替换时不能直接 cart = newCartObj,否则不会触发响应;需要:
Object.assign(cart, newCartObj);
// 或手动清空 items: cart.items.splice(0), 重置其他字段
`Object.assign` 写法较繁琐,且当对象属性更新逻辑复杂时需注意字段对齐。
- **只修改某些字段/数组操作**:如在购物车中增加、减少某项数量、移除某项、更新优惠券、增加浏览次数等,多是逐字段操作,更适合 `reactive`,写法简洁:
const cart = reactive({ items: [], couponCode: '', total: 0 });
function increase(idx) {
  cart.items[idx].quantity++;
  recalcTotal();
}

如果用 ref,写成 cartRef.value.items[idx].quantity++,多了 .value,可读性略差。

3.3 组合场景示例

以搜索筛选和分页为例,结合 ref/reactive:

<template>
  <input v-model="searchQuery" @keyup.enter="doSearch" placeholder="搜索商品" />
  <select v-model="filters.category">
    <option value="">全部</option>
    <option value="electronics">电子</option>
  </select>
  <div>
    <button @click="prevPage" :disabled="page <= 1">上一页</button>
    <span>第 {{ page }} 页</span>
    <button @click="nextPage">下一页</button>
  </div>
  <div v-if="loading">加载中...</div>
  <ul v-else>
    <li v-for="item in list" :key="item.id">{{ item.name }}</li>
  </ul>
</template>
<script setup>
import { ref, reactive } from 'vue';
const searchQuery = ref('');
const page = ref(1);
const loading = ref(false);
const filters = reactive({ category: '', priceRange: { min: null, max: null } });
const list = ref([]);
async function fetchData() {
  loading.value = true;
  // 构造参数
  const params = {
    q: searchQuery.value,
    category: filters.category,
    min: filters.priceRange.min,
    max: filters.priceRange.max,
    page: page.value
  };
  // 假设调用接口返回 items、hasMore
  const res = await fetchProducts(params);
  list.value = res.items;
  loading.value = false;
}
function doSearch() {
  page.value = 1;
  fetchData();
}
function prevPage() {
  if (page.value > 1) {
    page.value--;
    fetchData();
  }
}
function nextPage() {
  page.value++;
  fetchData();
}
</script>
  • searchQuery, page, loading, list 独立值用 reffilters 多字段用 reactive。两者结合,写法语义清晰、逻辑分明。

4. 解构与响应丢失:常见陷阱

  • reactive 解构后丧失响应

    const state = reactive({ a: 1, b: 2 });
    const { a, b } = state;
    // 这里的 a、b 都是普通值,不再是响应式。模板或 watch 无法再跟踪它们。
    • 解决:若想解构并保持响应,可用 toRefs

      import { reactive, toRefs } from 'vue';
      const state = reactive({ a: 1, b: 2 });
      const { a, b } = toRefs(state);
      // a、b 都是 ref,保持响应
  • ref 解构注意
    对于 const count = ref(0),一般直接用 count;不应做 const { value } = count,因为拿到的 value 是初始值,后续对 count.value 修改不会更新这个解构后的 value 变量,也不会触发响应。

  • 整体替换 vs 解构
    如果本来想整体替换 reactive 对象,解构后再合并新对象会更复杂。一般用 ref 包对象来明确表示整体替换。

5. 深度 vs 浅层响应

  • reactive 默认深度递归:内部嵌套对象/数组会在访问时或初始化时转为 Proxy。
  • ref 包对象:当值是对象或数组时,Vue 内部会对其做 reactive 处理,达到深度响应。但访问时仍需 .value
  • shallowReactive / shallowRef:在特殊场景下,如果不想对深层嵌套做自动响应,可使用浅响应 API。但多数情况下默认深度足够。

示例:若想对顶层字段变化跟踪,但不关心内部深层变化,可用 shallowReactive({ nested: { ... } })

6. 团队实践与语义统一

  • 保持一致性:团队可制定简单约定,比如:

    • 单值用 ref;多字段状态用 reactive
    • 若有大量整体替换场景,将对应对象用 ref 包裹,并在注释或文档中标明“此状态将整体赋值替换”。
    • 避免随意把对象都用 ref 包一层或把所有状态都放到一个大 reactive 对象,导致解构、替换、类型推断等复杂。
  • TypeScript 友好:无论 ref 还是 reactive,都有相应类型推断。可结合接口定义:

    interface Cart { items: CartItem[]; couponCode: string; total: number; }
    const cartRef = ref<Cart>({ items: [], couponCode: '', total: 0 });
    // 或 reactive:
    const cart = reactive<Cart>({ items: [], couponCode: '', total: 0 });
    • 如果用 reactive,类型推断里访问 cart.items 等一目了然;用 ref 包对象时访问需 .value.items,类型也清晰,但习惯上需注意。
  • 可读性与维护:若某状态对象很大且只在特定场景整体替换,ref 包对象能让代码一眼看出“这是一个整体”;若多个位置需单字段修改,用 reactive 写法更简洁。结合团队项目实际需求,选择更贴近业务意图的方式。

7. 性能与底层实现简述

  • 底层都是 Proxy & effectreactive 底层用 Proxy 拦截 get/set;ref 底层实现封装了对 .value 的 track/trigger,如果值是对象内部也会递归转 reactive。
  • 性能差异极小:两者在常规使用下性能相当。区别主要在语义和写法。
  • 初始化开销:reactive 在访问深层属性时会懒递归(或初始化时深度遍历,取决内部实现策略),而 ref 包基本类型开销更小;但在对象场景,开销差异在可接受范围。通常不必为性能过度担心,而是根据使用场景选择更清晰易维护的方式。

8. 常见误区纠正

  1. “所有状态都用 ref”

    • 虽然把对象用 ref(obj) 也能响应,但会导致 JS 逻辑里到处出现 .value,可读性差;解构/传参麻烦;整体替换与逐字段更新语义不够明确。
    • 正确做法应先判断:如果只是想更新字段,推荐 reactive;若经常整体赋新值,ref 包对象可考虑。
  2. “所有状态都用 reactive”

    • 当只需管理单一值时,用 reactive 会将其包在对象里(如 reactive({ count: 0 })),这写法冗余;并且整体替换需要 Object.assign,不够直观。
    • 对于标志位、计数、单值 API 返回数据、Boolean 开关等,推荐用 ref。
  3. 忽视解构导致响应丢失

    • 解构 reactive 对象字段时要用 toRefs;否则解构后的变量再修改无法触发视图更新。面试中若提到解构,需说明解决方案。
  4. 混用场景未做区分

    • 例如把整个表单数据既用 reactive 管理,也在某些地方把它赋值给 ref,再修改一半字段时容易混淆响应;要在代码风格上统一,明确何时整体替换、何时字段更新。

9. 总结

  • 核心认识refreactive 并非完全可互换,而是分别针对“独立值/整体替换”和“复合对象/逐字段更新”场景设计。理解两者语义、访问方式和解构注意。

  • 建议

    • 先分析业务需求:状态是单一值还是多字段聚合?是否需要整体替换或只是字段更新?是否会在多个地方解构或传递?
    • 按需选择:简单标志、计数、页码、loading、Boolean、ID 等用 ref;多个字段组合成配置、表单、购物车列表、复杂详情对象等用 reactive
    • 若有整体替换需求,也可用 ref 包对象。若用 reactive,注意用 Object.assign 或手动清理以触发响应。
    • 谨慎解构:了解 toRefs 用法;在 Composition API 里尽量直接操作 ref/reactive,避免无谓解构。
    • 团队约定:统一风格,避免不同开发者随意选择导致代码风格混乱。
  • 一句话概括

    “Vue3 里 ref 是给单一值或需要整体替换的状态包装响应式,reactive 是给对象/数组做深度响应,用在多字段状态更新更直观,两者语义不同、写法不同,应根据需求选用。”

到此这篇关于玩懂Vue3的ref和reactive的文章就介绍到这了,更多相关vue ref和reactive内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用Vue实现图片上传的三种方式

    使用Vue实现图片上传的三种方式

    在项目中经常会遇到图片上传功能,今天脚本之家小编给大家带来了使用Vue实现图片上传的三种方式,感兴趣的朋友一起看看吧
    2018-07-07
  • vue项目初始化过程中错误总结

    vue项目初始化过程中错误总结

    在Vue.js项目初始化和构建过程中,可能会遇到多种问题,首先,npm install过程中报错,如提示“No such file or directory”,建议删除package-lock.json文件后重新安装,在build或run时,若出现core-js相关错误
    2024-09-09
  • vue中本地静态图片路径写法

    vue中本地静态图片路径写法

    这篇文章给大家介绍了vue中本地静态图片路径写法及Vue.js中引用图片路径的方式,需要的朋友参考下吧
    2018-03-03
  • vue实现检测敏感词过滤组件的多种思路

    vue实现检测敏感词过滤组件的多种思路

    这篇文章主要介绍了vue编写检测敏感词汇组件的多种思路,帮助大家更好的理解和学习使用vue框架,感兴趣的朋友可以了解下
    2021-04-04
  • Vue布置导航守卫获取用户信息

    Vue布置导航守卫获取用户信息

    这篇文章主要为大家介绍了Vue布置导航守卫获取用户信息,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • vue-i18n实现中英文切换的方法

    vue-i18n实现中英文切换的方法

    这篇文章主要介绍了vue-i18n实现中英文切换的方法,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07
  • Vue报错error:0308010C:digital envelope routines::unsupported的解决方法

    Vue报错error:0308010C:digital envelope routines::unsupported

    这篇文章主要给大家介绍了关于Vue报错error:0308010C:digital envelope routines::unsupported的解决方法,文中通过图文将解决的办法介绍的非常详细,需要的朋友可以参考下
    2022-11-11
  • 解决在Vue中使用axios用form表单出现的问题

    解决在Vue中使用axios用form表单出现的问题

    今天小编就为大家分享一篇解决在Vue中使用axios用form表单出现的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-10-10
  • vue简单实现一个虚拟列表的示例代码

    vue简单实现一个虚拟列表的示例代码

    虚拟列表只渲染当前可视区域的列表,并不会将所有的数据渲染,本文主要介绍了vue简单实现一个虚拟列表的示例代码,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • vue前端img访问鉴权后端进行拦截的代码示例

    vue前端img访问鉴权后端进行拦截的代码示例

    路由拦截是一种在用户访问特定页面之前对其进行拦截和处理的机制,下面这篇文章主要给大家介绍了关于vue前端img访问鉴权后端进行拦截的相关资料,需要的朋友可以参考下
    2024-03-03

最新评论