文章目录

Vue3 Composition API中ref和reactive该怎么选

发布于 2026-04-20 10:26:48 · 浏览 3 次 · 评论 0 条

Vue3 Composition API中ref和reactive该怎么选

Vue3 引入 Composition API 后,响应式系统的构建方式发生了变化。核心的选择难题集中在 refreactive 两个 API 上。这两者虽然功能类似,但在使用场景和代码风格上有着本质区别。选择正确的 API 能避免很多“失去响应性”的常见 bug。


一、 理解核心区别

在开始选择之前,必须先搞清楚它们底层的运作逻辑。

ref:主要用于包装基本数据类型(如 stringnumberboolean)。它在内部将值包装在一个对象中,通过 .value 属性访问。

reactive:用于创建深层响应式对象。它直接返回一个响应式的 Proxy 对象,不需要 .value,但只能用于对象类型(Object、Array、Map、Set 等)。


二、 严格遵循的选择标准

按照以下三个步骤进行判断,可以覆盖 99% 的开发场景。

1. 第一步:检查数据类型

判断你要定义的数据是基本类型还是对象类型。

  • 如果是 stringnumberbooleannullundefined

    • 强制使用 ref
    • reactive 无法处理这些类型,不会产生任何响应式效果。
  • 如果是 ObjectArray

    • 进入下一步判断。

2. 第二步:检查是否需要整体替换

这是最容易踩坑的地方。当你打算将来把整个对象赋值给这个变量(例如重新从 API 获取数据覆盖旧数据)时。

  • 如果你需要整体赋值操作。

    • 优先使用 ref
    • ref 包装对象后,可以直接修改 .value 指向新的内存地址。
    • reactive 是一个 Proxy,如果直接赋值(如 userState = newUser),会切断响应性连接。
    // 正确示例:使用 ref 进行整体替换
    let user = ref({ name: 'Alice' })
    // ✅ 获取新数据后直接覆盖
    user.value = await fetchUser() 
    
    // 错误示例:使用 reactive 尝试整体替换
    let user = reactive({ name: 'Alice' })
    // ❌ 这样做会失去响应性
    user = await fetchUser() 

3. 第三步:检查解构与模板使用习惯

如果你需要将对象的属性解构出来单独使用,或者在模板中不想总是看到 .value

  • 如果你喜欢解构赋值

    • reactive 解构后会丢失响应性。
    • 若要解构,必须配合 toRefs 使用。或者直接使用 ref 定义每一个属性。
    • 建议:如果只是简单的一层对象,直接用 ref 定义属性,或者对 reactive 对象使用 toRefs
    import { toRefs } from 'vue'
    
    const state = reactive({ count: 0, name: 'Vue' })
    // ✅ 使用 toRefs 保持响应性
    const { count, name } = toRefs(state)
  • 如果你在模板中追求极致的语法简洁

    • 在模板中,ref 会自动解包(不需要写 .value),而 reactive 直接访问属性。
    • 这一点上两者在模板中的体验几乎一致,但在 JS 逻辑中 ref 需要 .value

三、 决策流程图

当你在代码中犹豫不决时,按照以下流程进行决策。

graph TD A["开始: 定义响应式数据"] --> B{"数据类型是
String/Number/Boolean?"} B -- 是 --> C["必须使用 ref"] B -- 否 --> D{"需要整体替换
对象吗?"} D -- 是 --> C D -- 否 --> E{"是否需要解构
或频繁传递属性?"} E -- 是 --> F["使用 reactive + toRefs"] E -- 否 --> G["使用 reactive 或 ref 均可"] G --> H{"团队代码风格偏好"} H -- 统一风格 --> C H -- 统一风格 --> F

四、 常见陷阱与解决方案

在使用过程中,有两个非常典型的错误场景,必须通过特定方式避免。

陷阱 1:试图给 reactive 属性赋值新对象

当你有一个 reactive 定义的表单对象,提交后想清空它。

const form = reactive({
  username: '',
  password: ''
})

// ❌ 错误做法:直接赋值一个新的空对象,会导致响应性丢失
form = { username: '', password: '' }

// ✅ 正确做法:遍历属性逐个清空,或者使用 Object.assign
Object.assign(form, { username: '', password: '' })

陷阱 2:数组在 reactive 中的索引赋值

直接通过索引修改数组元素在 Vue3 中是响应式的(不同于 Vue2),但如果配合解构使用则需小心。

const list = reactive([1, 2, 3])

// ✅ 直接通过索引赋值(Vue3 中是响应式的)
list[0] = 99

// ✅ 使用数组的修改方法
list.push(4)
list.splice(0, 1)

五、 最佳实践总结表

为了方便记忆,下表总结了两者的核心差异与适用场景。

特性维度 ref reactive
适用数据类型 基本类型、对象 仅对象/数组
访问方式 (JS中) 需要 .value 直接访问属性
访问方式 (模板中) 自动解包,直接访问 直接访问属性
整体替换对象 ✅ 支持 ❌ 不支持(会断开引用)
解构赋值 ✅ 解构后需用 .value,保留响应性 ❌ 解构后丢失响应性(需 toRefs)
推荐场景 单一基本值、需要整体替换的对象 深层嵌套且结构固定的复杂对象
TypeScript 支持 需要泛型指定类型 推断能力强,通常无需泛型

六、 实际代码模板

根据上述规则,以下是一个标准的组件状态定义模板。

import { ref, reactive, computed, toRefs } from 'vue'

export default {
  setup() {
    // 场景 1:基本类型,使用 ref
    const count = ref(0)

    // 场景 2:简单的计数器逻辑
    const increment = () => {
      count.value++
    }

    // 场景 3:复杂深层对象,结构固定,使用 reactive
    const userState = reactive({
      profile: {
        name: 'User',
        age: 20,
        address: {
          city: 'Beijing'
        }
      },
      isLoggedIn: false
    })

    // 场景 4:需要从 API 获取数据并整体替换,使用 ref
    const userData = ref(null)

    const fetchUserData = async () => {
      const res = await api.getUser()
      // ✅ 直接替换
      userData.value = res.data
    }

    // 场景 5:将 reactive 对象的属性解构暴露给模板
    // 注意:toRefs 只能用于响应式对象,不能用于普通对象
    const { profile, isLoggedIn } = toRefs(userState)

    return {
      count,
      increment,
      // 返回解构后的属性,在模板中可以直接用 profile.name
      profile, 
      isLoggedIn,
      userData
    }
  }
}

最终建议:如果你的团队没有强制的代码规范,优先使用 ref。虽然写 .value 稍显繁琐,但它的心智负担最小,能处理所有类型的替换问题,且在解构时配合 toRefs 非常灵活。reactive 则最适合用于那些一旦定义就很少改变整体结构,只是频繁修改内部属性的复杂状态树。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文