字数 1224,阅读大约需 7 分钟
很多刚开始用Vue3的朋友,都会对ref有个共同的疑问:
为什么我声明了一个响应式数据,每次用的时候还得在后面加个.value? 这多麻烦啊,Vue2的data里直接写不就完了吗?
今天,我们就来把这个事儿彻底聊明白。你会发现,这个小小的.value,背后藏着Vue3设计思路的大变化。
先看看ref是怎么用的
在Vue3里,如果你想定义一个响应式的基本类型数据(比如字符串、数字、布尔值),很可能会用到ref。
import { ref } from 'vue'
// 定义一个响应式数据
const count = ref(0)
const message = ref('Hello Vue3')
// 在JavaScript里访问和修改它,必须用 .value
console.log(count.value) // 输出: 0
count.value = 1
console.log(count.value) // 输出: 1
在模板里,你却又不用写.value,Vue会自动帮你“解开”。
<template>
<div>
<!-- 这里直接使用 count,不用写 .value -->
<p>{{ count }}</p>
<button @click="count++">增加</button>
</div>
</template>
你看,在JS里要.value,在模板里又不用。这种不一致的感觉,就是困惑的来源。
为什么Vue2不需要,Vue3却需要?
在Vue2里,所有data选项里的数据,都会被Vue自动转换成带有getter/setter的响应式对象。
// Vue2 选项式API
export default {
data() {
return {
count: 0 // Vue内部会把它变成响应式的
}
},
methods: {
increment() {
this.count++ // 直接访问即可
}
}
}
这很方便,但有个问题:响应式转换是“侵入式”的。
Vue必须在一开始就遍历你整个data对象,把所有属性都“加工”一遍。对于大型应用或复杂嵌套对象,这有性能开销。
Vue3提供了两种创建响应式数据的主要工具:
-
• reactive(): 用于处理对象和数组。它像Vue2那样,返回一个整个对象的响应式代理。 -
• ref(): 用于处理任何类型的值,尤其是基本类型(string, number, boolean)。
ES6的Proxy API--Vue3响应式的基石,Proxy只能代理对象,不能代理基本类型值(如数字0、字符串‘hello’)。 数字、字符串在JS里是“值类型”,不是“引用类型”,它们没有属性,也无法被Proxy包装。
所以,ref想出了一个聪明的办法:
如果你给我一个基本类型值,我就把它装进一个普通的JavaScript对象({ value: ... })里,然后对这个包装对象使用reactive(其内部基于Proxy)。 这样,任何类型的数据都能变成响应式的了。
// ref 的简化版原理
function myRef(initialValue) {
// 1. 把值装进一个对象
const wrapper = {
value: initialValue
}
// 2. 用 reactive 让这个包装对象变成响应式
return reactive(wrapper)
}
因此,.value就是你访问这个“包装对象”内部真实数据的唯一途径。
一些常见的疑惑和技巧
1. 为什么模板里不用写.value?
这是因为Vue的模板编译器足够智能。当它在模板中遇到一个ref时,会自动进行解包(unwrapping)。在模板渲染的上下文中,直接使用count等价于使用count.value。这是一种为开发者提供的便利语法糖。
2. ref和reactive怎么选?
-
• 用 ref当: -
• 你的数据是基本类型( string,number,boolean)。 -
• 你定义的数据在未来可能被整个替换(比如从服务器获取一组新数据)。 -
• 你想在组合式函数中返回一个响应式数据,让调用者明确知道这是一个 Ref。 -
• 用 reactive当: -
• 你有一个不需要整体替换的复杂对象或数组。 -
• 你想享受直接访问和修改属性( obj.key)的便利,而不用.value。
3. 如何减少.value的烦恼?
-
• 使用计算属性(computed):计算属性会自动解包内部的 ref。const count = ref(0)
const doubleCount = computed(() => count.value * 2) // 在计算函数内需要 .value
console.log(doubleCount.value) // 访问计算结果也需要 .value?不!
// 在模板中,直接使用 {{ doubleCount }} 即可,它也是自动解包的。 -
• 使用解构函数:Vue3提供了 toRefs函数,可以将一个reactive对象的所有属性转换为ref,便于解构而不丢失响应性。import { reactive, toRefs } from 'vue'
const state = reactive({ foo: 1, bar: 2 })
// 转换为 ref,解构后每个属性仍需 .value
const { foo, bar } = toRefs(state)
console.log(foo.value) // 1
最后
那个看似多余的.value,其实是Vue3在灵活性和开发体验之间做出的一个权衡。它用一点语法上的代价,换来了:
-
• 对基本类型响应式的支持(这是Proxy做不到的)。 -
• 更明确的响应式数据标识(看到 Ref就知道要.value)。 -
• 更统一的响应式API(用 ref这一种方式可以处理所有数据类型)。
🚀专注前沿技术拆解 | 每日 9:00 更新
👇 关注 | 点赞 | 分享,我们共同进化
🔥 热门文章推荐:

