×

ref到底是什么?和reactive有啥本质区别?

作者:Terry2025.05.17来源:Web前端之家浏览:34评论:0
关键词:refreactive

vue 3.4

Vue 3.4发布后,不少开发者在社群里讨论:“ref的用法好像变简单了?”“之前总搞不清什么时候用ref,现在有啥新变化?”作为Vue响应式系统的核心工具之一,ref的重要性不言而喻,但实际开发中,新手容易被.value搞懵,老手也可能在某些边界场景踩坑,今天咱们就用问答形式,把Vue 3.4里ref的那些事儿彻底说清楚。 ref是Vue提供的“值包装器”,专门用来为基本类型值(如数字、字符串、布尔值)创建响应式数据,比如你想让一个变量num在修改时触发视图更新,直接用let num = 1是不行的,这时候就需要用ref包裹:const num = ref(1),这时候num就变成了一个“响应式引用”,修改时要通过num.value = 2,视图才会跟着变。

那reactive呢?它是专门为对象(包括数组、Map等)设计的响应式方案,通过Proxy对对象进行深层代理,比如const obj = reactive({ name: '张三' }),修改obj.name时,视图会自动更新。

两者的核心区别在于适用场景:

  • ref主要处理基本类型和“需要被引用的对象”(后面会讲);

  • reactive只能处理对象,且无法直接替换整个对象(比如obj = { name: '李四' }会丢失响应式)。

举个例子:如果你要在组件间传递一个数字,用ref更方便,因为reactive无法直接传递基本类型;但如果要管理一个复杂的用户信息对象(包含姓名、年龄、地址),用reactive更直观,不需要频繁写.value。

为啥对象用ref包裹后,访问属性不用写.value?

这是Vue 3.4重点优化的“自动解包”特性,以前用ref包裹对象时,比如const user = ref({ name: '张三' }),访问user.name需要写user.value.name,否则会报错,但现在Vue做了优化:当ref的值是对象时,会自动用reactive包裹,并且在访问属性时自动解包.value

比如在模板中:

<template>
  <div>{{ user.name }}</div> <!-- 直接访问,不用写.value -->
</template>

在脚本中:

// 修改属性也不用写.value
user.value.name = '李四' // 旧写法(仍可用)
user.name = '李四'       // 新写法(Vue 3.4支持,更简洁)

不过要注意,这种自动解包只适用于对象类型的ref值,如果ref的值是基本类型(如数字、字符串),访问时必须用.value,否则拿到的是ref对象本身,比如const count = ref(0),在脚本中直接打印count得到的是{ value: 0 },而count.value才是0。

.value到底什么时候必须写?这3种场景要注意!

虽然Vue 3.4优化了自动解包,但在以下场景中,.value依然是“必选项”:

基本类型ref在脚本中访问时
比如const message = ref('你好'),在方法里修改内容必须写message.value = '再见',如果直接写message = '再见',相当于把ref对象本身替换成了字符串,会丢失响应式——这是新手最常踩的坑!

函数参数传递时
假设你有一个函数updateValue,接收一个ref参数:

function updateValue(target) {
  target.value = '新值' // 必须写.value,否则无法修改原ref的值
}

如果不写.value,直接target = '新值',只是修改了函数内部的局部变量,原ref不会变化。

计算属性返回ref时
计算属性(computed)默认返回的是ref对象,如果在计算属性中返回另一个ref,访问时需要用.value。

const base = ref(10)
const double = computed(() => base.value * 2)
console.log(double.value) // 输出20(必须写.value)

Vue 3.4给ref带来了哪些新变化?这2点最实用!

相比Vue 3.2/3.3,3.4版本对ref的优化主要体现在自动解包的边界处理与组合式API的配合上:

数组和集合类型的自动解包更智能
以前用ref包裹数组时,比如const list = ref([1, 2, 3]),调用list.push(4)需要写list.value.push(4),否则会报错,但Vue 3.4优化后,允许直接调用list.push(4),因为内部会自动处理.value的访问,不过要注意,如果是替换整个数组(如list = [1,2,3,4]),还是需要写list.value = [1,2,3,4],否则会丢失响应式。

组件模板中支持更灵活的解构
在setup函数中返回ref时,以前如果解构会丢失响应式。

setup() {
  const count = ref(0)
  return { count }
}
// 模板中可以直接用{{ count }}(自动解包)

但如果解构返回对象:

const { count } = setup()
// 这时候count是原始ref对象,模板中需要写{{ count.value }}

Vue 3.4优化了这一点,允许在模板中直接使用解构后的变量名,自动处理.value的解包,减少了不必要的模板代码。

用ref开发时,这4个坑90%的人都踩过!

  1. 直接替换ref对象导致响应式丢失
    错误写法:

    const user = ref({ name: '张三' })
    user = { name: '李四' } // 错误!这是替换了user变量,原ref失效

    正确写法:

    user.value = { name: '李四' } // 修改ref的value属性,保留响应式
  2. 在watch中忘记处理深层对象
    如果用ref包裹一个对象,并且需要监听对象内部属性的变化,需要开启deep选项:

    const user = ref({ name: '张三' })
    watch(user, (newVal, oldVal) => {
    console.log('用户信息变化了')
    }, { deep: true }) // 必须加deep,否则只会监听整个对象是否被替换
  3. 在v-for中错误使用ref
    如果在v-for循环中为每个元素绑定ref,需要用数组或对象来存储ref,否则只会保留最后一个元素的引用。

    <template>
    <div v-for="(item, index) in list" :ref="el => refs[index] = el">{{ item }}</div>
    </template>
    <script setup>
    const list = ref(['a', 'b', 'c'])
    const refs = ref([]) // 用数组存储每个元素的ref
    </script>
  4. 在异步操作中忘记处理.value
    比如在axios请求后更新ref的值:

    async function fetchData() {
    const res = await axios.get('/api/data')
    // 错误:直接赋值会丢失响应式
    // data = res.data 
    // 正确:通过.value更新
    data.value = res.data
    }

ref的核心是“值的响应式引用”

说到底,ref的设计是为了解决基本类型无法被Proxy直接代理的问题,通过包裹一个“值容器”(即ref对象),Vue实现了对基本类型的响应式支持,而Vue 3.4的优化,让对象类型的ref使用更接近reactive,降低了学习成本。

实际开发中,记住这3个原则就能少踩坑:

  • 基本类型用ref,对象/数组优先用reactive;

  • 修改ref的值时,基本类型必须通过.value,对象类型可直接修改属性(但替换整个对象时仍需.value);

  • 传递ref时,尽量保持其引用不变,避免直接替换变量。

下次遇到“为什么我的数据更新了视图没动?”的问题,先检查是不是ref的.value没用对——这招屡试不爽!

您的支持是我们创作的动力!
温馨提示:本文作者系Terry ,经Web前端之家编辑修改或补充,转载请注明出处和本文链接:
https://jiangweishan.com/article/refreactivedsf892834.html

网友评论文明上网理性发言 已有0人参与

发表评论: