Vue3中ref与reactive实战指南:使用场景与代码示例

在Vue3的响应式系统中,refreactive是创建响应式数据的核心API,但许多开发者在实际开发中常困惑于"何时用ref,何时用reactive"。本文结合真实开发场景,通过完整代码示例拆解两者的适用场景、核心差异及避坑要点,帮助开发者高效选型。

目录

  1. 核心概念快速回顾
  2. 分场景实战:ref与reactive的正确选型
    • 场景1:基础数据类型(数字/字符串/布尔)
    • 场景2:复杂对象(多字段关联数据)
    • 场景3:数组/列表数据(增删改操作)
    • 场景4:需整体替换的数据(接口刷新场景)
    • 场景5:解构响应式数据(保持响应性)
  1. 场景选择指南表(速查版)
  2. 常见问题答疑(避坑要点)
  3. 总结

1. 核心概念快速回顾

在进入场景前,先明确refreactive的本质差异,为后续选型打基础:

|-----------|-----------------|-------------------|
| 特性 | ref | reactive |
| 本质 | 响应式"容器"(包装任意类型) | 响应式"代理"(仅代理对象/数组) |
| 访问方式(脚本中) | 需通过.value | 直接访问属性(无.value) |
| 整体替换支持 | 支持(替换后仍保持响应性) | 不支持(替换会丢失响应性) |
| 适用类型 | 所有类型(优先基本类型) | 仅对象/数组 |

2. 分场景实战:ref与reactive的正确选型

以下场景均基于Vue3的<script setup>语法(目前主流开发模式),代码可直接复制运行。

场景1:基础数据类型(数字/字符串/布尔)

适用场景 :存储单个简单值,如计数器、开关状态、输入框文本、标题文本等。
推荐APIref(唯一能响应式包装基本类型的API)

代码示例:计数器与开关状态
复制代码
<template>
  <div class="basic-type-demo">
    <h3>计数器:{{ count }}</h3>
    <button @click="increment">+1</button>
    <button @click="reset">重置</button>
    <div class="toggle-demo" style="margin-top: 20px;">
      <p>是否显示详情:{{ isShowDetail }}</p>
      <button @click="toggleShow">{{ isShowDetail ? '隐藏' : '显示' }}</button>
    </div>
  </div>
</template>
<script setup>
import { ref } from 'vue'

// 1. 数字类型:计数器
const count = ref(0) // 初始值0,包装为ref对象
const increment = () => {
  count.value++ // 脚本中必须通过.value修改
}
const reset = () => {
  count.value = 0 // 直接替换值,仍保持响应性
}

// 2. 布尔类型:开关状态
const isShowDetail = ref(true)
const toggleShow = () => {
  isShowDetail.value = !isShowDetail.value // 切换布尔值
}
</script>
为什么不用reactive

reactive无法直接包装基本类型(如reactive(0)会报错),若强行用对象包裹(如reactive({ count: 0 })),会多一层不必要的嵌套,修改时需写obj.count++,不如ref.value简洁。

场景2:复杂对象(多字段关联数据)

适用场景 :存储多字段关联的数据,如用户信息、商品详情、表单配置、页面状态等。
推荐APIreactive(直接操作属性,无需.value,代码更简洁)

代码示例:用户信息管理
复制代码
<template>
  <div class="complex-object-demo">
    <h3>用户信息卡片</h3>
    <p>姓名:{{ user.name }}</p>
    <p>年龄:{{ user.age }}</p>
    <p>地址:{{ user.address.city }}-{{ user.address.district }}</p>
    <button @click="growUp" style="margin-top: 10px;">年龄+1</button>
    <button @click="updateAddress" style="margin-left: 10px;">更新地址</button>
  </div>
</template>
<script setup>
import { reactive } from 'vue'

// 复杂对象:多字段+嵌套结构
const user = reactive({
  name: '张三',
  age: 24,
  address: {
    city: '上海',
    district: '浦东新区'
  }
})

// 直接修改属性(无.value)
const growUp = () => {
  user.age++ // 直接操作嵌套属性
}

// 修改嵌套对象
const updateAddress = () => {
  user.address = {
    city: '北京',
    district: '朝阳区'
  }
}
</script>
为什么不用ref

若用ref包装对象(如const user = ref({ name: '张三' })),修改属性时需写user.value.name = '李四',比reactiveuser.name多一层.value,嵌套越深越繁琐。

场景3:数组/列表数据(增删改操作)

适用场景 :处理列表类数据,如待办事项、商品列表、表格数据,需频繁执行push/splice/filter等操作。
推荐APIreactive(原生数组方法直接用,无需.value

代码示例:待办列表(增删功能)
复制代码
<template>
  <div class="array-demo">
    <h3>待办列表</h3>
    <!-- 新增待办输入框 -->
    <input 
      v-model="newTodoText" 
      placeholder="请输入待办" 
      @keyup.enter="addTodo"
    >
    <button @click="addTodo">添加</button>
    <!-- 待办列表渲染 -->
    <ul style="list-style: none; padding: 0; margin-top: 10px;">
      <li v-for="(todo, index) in todoList" :key="index" style="margin: 5px 0;">
        {{ todo }}
        <button @click="deleteTodo(index)" style="margin-left: 10px; color: red;">
          删除
        </button>
      </li>
    </ul>
  </div>
</template>
<script setup>
import { reactive, ref } from 'vue'

// 数组用reactive:直接调用数组方法
const todoList = reactive(['学习Vue3响应式', '写技术博客'])

// 输入框文本用ref(基本类型)
const newTodoText = ref('')

// 新增待办
const addTodo = () => {
  if (newTodoText.value.trim()) {
    todoList.push(newTodoText.value) // 直接用push,无.value
    newTodoText.value = '' // 清空输入框
  }
}

// 删除待办
const deleteTodo = (index) => {
  todoList.splice(index, 1) // 直接用splice
}
</script>
为什么不用ref

若用ref包装数组(如const todoList = ref(['a', 'b'])),执行数组方法时需写todoList.value.push(...),比reactivetodoList.push(...)多一层.value,不够直观。

场景4:需整体替换的数据(接口刷新场景)

适用场景 :数据需被整体覆盖,如接口请求后用新数据替换旧数据、切换Tab时刷新列表。
推荐APIrefreactive整体替换会丢失响应性)

代码示例:商品数据接口刷新
复制代码
<template>
  <div class="replace-data-demo">
    <h3>商品详情</h3>
    <p>名称:{{ product.name }}</p>
    <p>价格:{{ product.price }}元</p>
    <p>库存:{{ product.stock }}件</p>
    <button @click="fetchNewProduct" style="margin-top: 10px;">
      加载新商品
    </button>
  </div>
</template>
<script setup>
import { ref } from 'vue'

// 用ref包装对象:支持整体替换
const product = ref({
  name: '旧款手机',
  price: 1999,
  stock: 50
})

// 模拟接口请求:整体替换数据
const fetchNewProduct = () => {
  // 模拟接口返回新数据
  const newProductData = {
    name: '新款手机',
    price: 2999,
    stock: 100
  }

  // 整体替换ref的值,仍保持响应性
  product.value = newProductData
}
</script>
为什么不用reactive

若用reactive(如const product = reactive({ name: '旧款手机' })),直接替换整个对象会丢失响应性:

复制代码
// 错误示例:reactive直接替换会丢失响应性
product = { name: '新款手机' } // 此时product不再是响应式代理

场景5:解构响应式数据(保持响应性)

适用场景 :需要解构响应式对象的属性(如组件传参、拆分逻辑),且解构后仍需保持响应性。
推荐方案reactive + toRefstoRefsreactive属性转为ref

代码示例:解构用户信息并保持响应性
复制代码
<template>
  <div class="destructure-demo">
    <h3>解构后的用户信息</h3>
    <p>姓名:{{ name }}</p>
    <p>年龄:{{ age }}</p>
    <button @click="age++">年龄+1</button>
  </div>
</template>
<script setup>
import { reactive, toRefs } from 'vue'

// 1. 用reactive定义响应式对象
const user = reactive({
  name: '李四',
  age: 28,
  gender: '男'
})

// 2. 用toRefs解构:将属性转为ref对象
const { name, age } = toRefs(user) 
// 此时name、age是ref对象,脚本中修改需用.age.value,模板中自动解包

// 3. 修改解构后的属性(仍保持响应性)
// age.value++ // 脚本中修改方式
</script>
避坑提示:直接解构reactive会丢失响应性

若不使用toRefs,直接解构reactive对象,得到的属性是普通值,修改后不会触发视图更新:

复制代码
// 错误示例:直接解构reactive会丢失响应性
const { name } = user 
name = '王五' // 无响应性,视图不更新

3. 场景选择指南表(速查版)

|---------------|-------------------|------------------------|---------------------------|
| 场景描述 | 推荐API/方案 | 核心原因 | 避坑提示 |
| 基本类型(数字/字符串等) | ref | 唯一支持基本类型的响应式API | 脚本中必须用.value修改 |
| 复杂对象(多字段) | reactive | 直接操作属性,无.value,代码简洁 | 不能整体替换对象 |
| 数组/列表(增删改) | reactive | 原生数组方法(push/splice)直接用 | 避免用ref包装数组(多一层.value) |
| 需整体替换的数据 | ref | reactive整体替换会丢失响应性 | 替换时直接修改.value |
| 解构后需保持响应性 | reactive + toRefs | toRefs将属性转为ref,保持响应性 | 不可直接解构reactive |

4. 常见问题答疑(避坑要点)

Q1:ref能包装对象吗?

能。ref包装对象时,内部会自动调用reactive生成代理,但访问属性需用.value(如user.value.name),不如reactive直接。

Q2:reactive数组如何清空?

直接用array.length = 0array.splice(0, array.length),无需整体替换:

复制代码
const todoList = reactive(['a', 'b'])
todoList.length = 0 // 正确:清空数组且保持响应性

Q3:ref解构后需要toRefs吗?

不需要。ref本身是容器,解构单个ref对象(如const { value: count } = ref(0))无意义,直接使用count.value即可。toRefs仅用于reactive对象的解构。

Q4:组件传参时,ref和reactive怎么选?

  • 传基本类型:用ref,子组件接收后直接用.value访问;
  • 传对象/数组:用reactive + toRefs,子组件接收ref类型的属性,避免直接传reactive对象(防止子组件意外修改父组件数据)。

5. 总结

refreactive没有绝对的"优劣",核心是按场景选型

  • 优先用ref:基本类型、需整体替换的数据;
  • 优先用reactive:复杂对象、数组列表;
  • 解构需响应性:搭配toRefs

掌握两者的本质差异和场景边界,能有效避免"响应性丢失"等问题,提升Vue3开发效率。建议结合本文代码示例动手实践,加深理解!

#Vue3 #ref与reactive #响应式系统 #前端开发 #Vue实战

相关推荐
peachSoda73 小时前
封装一个不同跳转方式的通用方法(跳转外部链接,跳转其他小程序,跳转半屏小程序)
前端·javascript·微信小程序·小程序
宠友信息3 小时前
java微服务驱动的社区平台:友猫社区的功能模块与实现逻辑
java·开发语言·微服务
驰羽3 小时前
[GO]golang接口入门:从一个简单示例看懂接口的多态与实现
开发语言·后端·golang
熊猫钓鱼>_>3 小时前
TypeScript前端架构与开发技巧深度解析:从工程化到性能优化的完整实践
前端·javascript·typescript
ii_best3 小时前
IOS/ 安卓开发工具按键精灵Sys.GetAppList 函数使用指南:轻松获取设备已安装 APP 列表
android·开发语言·ios·编辑器
王夏奇3 小时前
C++友元函数和友元类!
开发语言·c++
Full Stack Developme3 小时前
jdk.random 包详解
java·开发语言·python
懒羊羊不懒@3 小时前
Java基础入门
java·开发语言
JYeontu4 小时前
肉眼难以分辨 UI 是否对齐,写个插件来辅助
前端·javascript