本文导读

    ref 和 reactive 到底怎么选?别再混用了!这篇文章用大白话讲透两者的本质区别,结合真实案例、watch 陷阱和最佳实践,告诉你什么时候该用哪个,帮你写出更清爽、少踩坑的 Vue3 代码。

前言

refreactiveVue3Composition API里最常用的两个核心成员,专门用来创建响应式数据的。 啥叫响应式呢?就是数据一变,页面自动跟着更新,不用你手动刷新。 其实它们干的是一件事就是:让Vue能监听到你的数据变化

但它们用起来不一样,适合的场景也不一样。很多人在用的时候都是凭感觉选,到底该用哪个?一直都觉得很模糊。

先简单认识一下:

ref:可以包装任何类型的值,包括基本类型(数字、字符串等)和对象类型。使用时需要通过.value来访问和修改值。 reactive:只能包装对象类型(包括数组)。使用时直接访问属性即可,不需要.value

b0ca79428871de1434b721295ba9daef.jpg

下面咱们通过实际例子来看一下。

一、区别

// ref 啥都能包
const count = ref(0)          // 数字
const name = ref('小明')       // 字符串  
const isActive = ref(false)   // 布尔值
const user = ref({ age: 18 }) // 对象
const list = ref([1, 2, 3])   // 数组

// reactive 只接对象/数组
const user = reactive({ age: 18 }) // 对象
const list = reactive([1, 2, 3])   // 数组
const count = reactive(0)         // 报错

二、优缺点

ref

优点:
  1. 什么都能包:基本类型、对象、数组,来者不拒
  2. 赋值简单:直接 xxx.value = newValue
  3. 解构安全:不用怕响应式丢失
  4. 类型推导好TypeScript支持完美
缺点:
  1. 总要写 .value:有点烦人
  2. 模板中也要 .value:不过 Vue 会自动解包

reactive

优点:
  1. 不用写 .value:直接访问属性
  2. 相关联数据组织性好:类似Vue2data
缺点:
  1. 解构大坑:直接解构容易丢失响应式
  2. 赋值限制:不能整个重新赋值
  3. 类型推导有时抽风:TS环境下偶尔会出现问题

三、watch监听中的差异

这里是最容易踩坑的地方!

监听 ref

const count = ref(0)
const user = ref({ name: '小明', age: 18 })

// 监听基本类型 ref
watch(count, (newVal, oldVal) => {
  console.log('count变化:', newVal, oldVal)
})

// 监听对象 ref - 需要深度监听
watch(user, (newVal, oldVal) => {
  console.log('user变化:', newVal, oldVal)
}, { deep: true }) // 必须加 deep!

// 监听对象 ref 的特定属性
watch(() => user.value.name, (newVal, oldVal) => {
  console.log('name变化:', newVal, oldVal)
})

监听 reactive

const state = reactive({
  count: 0,
  user: { name: '小明', age: 18 }
})

// 自动深度监听,不需要 deep: true
watch(state, (newVal, oldVal) => {
  console.log('state变化:', newVal, oldVal)
}) 

// 推荐:监听特定属性
watch(() => state.count, (newVal) => {
  console.log('count变了', newVal)
})

// 监听嵌套属性
watch(() => state.user.name, (newVal) => {
  console.log('名字变了', newVal)
})

// 监听多个属性
watch([() => state.count, () => state.user.name], ([newCount, newName]) => {
  console.log('count或name变了', newCount, newName)
})

watch 的重要区别

1.深度监听:

  • ref对象需要手动{ deep: true }
  • reactive自动深度监听

2.旧值获取:

  • reactive的旧值和新值相同(Proxy特性)
  • ref的旧值正常

3.性能影响:

  • reactive自动深度监听,可能影响性能
  • ref可以精确控制监听深度

四、案例

案例1:表单处理 - 推荐 reactive

// 相关联的表单数据,用 reactive 更合适
const form = reactive({
  username: '',
  password: '',
  remember: false,
  errors: {}
})

// 验证函数
const validateForm = () => {
  form.errors = {}
  if (!form.username) {
    form.errors.username = '用户名不能为空'
  }
  // ...其他验证
}

案例2:API 数据加载 - 推荐 ref

// API 返回的数据,经常需要重新赋值,用 ref
const data = ref(null)
const loading = ref(false)
const error = ref(null)

const fetchData = async () => {
  loading.value = true
  try {
    const response = await fetch('/api/data')
    data.value = await response.json() // 直接赋值,美滋滋
  } catch (err) {
    error.value = err.message
  } finally {
    loading.value = false
  }
}

案例3:组件状态管理 - 看情况

// 方案1:用 reactive(状态相关联)
const modal = reactive({
  isOpen: false,
  title: '',
  content: '',
  loading: false
})

// 方案2:用多个 ref(状态相对独立)
const isModalOpen = ref(false)
const modalTitle = ref('')
const modalContent = ref('')
const modalLoading = ref(false)

案例4:列表操作 - 强烈推荐 ref

const list = ref([])

// 添加项目
const addItem = (item) => {
  list.value = [...list.value, item] // 重新赋值,安全
}

// 删除项目
const removeItem = (id) => {
  list.value = list.value.filter(item => item.id !== id)
}

// 清空列表
const clearList = () => {
  list.value = [] // 直接赋值,不会丢失响应式
}

如果用reactive来做列表:

const list = reactive([])

// 添加项目 - 只能用 push
const addItem = (item) => {
  list.push(item) // 可以,但不够直观
}

// 删除项目 - 需要找到索引
const removeItem = (id) => {
  const index = list.findIndex(item => item.id === id)
  if (index !== -1) {
    list.splice(index, 1) // 有点麻烦
  }
}

// 清空列表 - 只能修改长度
const clearList = () => {
  list.length = 0 // 能工作,但有点 hack
}

五、组合式函数中的选择

这是决定用哪个的关键场景!

返回多个 ref:灵活好用

function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const double = computed(() => count.value * 2)
  const increment = () => count.value++

  return { count, double, increment }
}

// 使用时:
const { count, double, increment } = useCounter()

返回 reactive:结构固定

function useUser() {
  const state = reactive({
    user: null,
    loading: false,
    error: null
  })

  const fetchUser = async (id) => {
    state.loading = true
    try {
      state.user = await fetchUserById(id)
    } catch (err) {
      state.error = err.message
    } finally {
      state.loading = false
    }
  }

  return { ...toRefs(state), fetchUser } // 必须用 toRefs!
}

// 使用时:
const { user, loading, error, fetchUser } = useUser()

明显看到,返回多个ref更简单直接。

六、混合使用模式(推荐)

// 基本类型和需要重新赋值的用 ref
const loading = ref(false)
const error = ref(null)
const data = ref(null)

// 相关联的数据用 reactive
const form = reactive({
  username: '',
  password: '',
  remember: false
})

// 这样写,既清晰又安全

七、性能

其实在大多数情况下,性能差异可以忽略不计,推荐如下:

  1. 大量基本类型:用ref,因为reactive需要创建Proxy对象
  2. 大对象但只关心部分属性:用多个ref,避免不必要的深度监听
  3. 频繁重新赋值:用refreactive不能直接重新赋值

总结

优先使用ref的场景: -基本类型(数字、字符串、布尔值) -DOM 引用和组件引用 -需要频繁重新赋值的变量 -API 返回的数据 -组合式函数的返回值 -列表数据

考虑使用reactive的场景: -相关联的表单数据 -复杂的组件状态 -配置对象 -不需要重新赋值的对象

希望这篇分析对你有帮助。如果有不同意见,欢迎在评论区理性讨论!

往期精彩

《工作 5 年没碰过分布式锁,是我太菜还是公司太稳?网友:太真实了!》

《Java 订单超时未支付,如何自动关闭?掌握这 3 种方案,轻松拿 offer!》

《写给小公司前端的 UI 规范:别让页面丑得自己都看不下去》

《终于找到 Axios 最优雅的封装方式了,再也不用写重复代码了》

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]