Vue3 unref到底是什么?什么时候用才不踩雷?
刚入门Vue3 Composition API时,很多开发者会被ref、reactive绕得晕头转向,好不容易理清了.value的写法,又蹦出来个unref,它到底是干嘛的?明明写个.value就能拿到值,为什么还要多写一层函数?有没有可能用错反而出问题?今天就把这些疑问一次性讲明白。
先搞懂Vue3里的“响应式引用”是什么
要说unref,得先回顾下它服务的对象——ref和computed创建的响应式引用,Vue3为了让基本类型(比如数字、字符串、布尔值)也能实现响应式,把它们包成了一个有value属性的对象;computed不管计算结果是基本类型还是引用类型,默认也会返回这种带value的“包装对象”,你可以把这个包装想象成给普通物品套了个快递盒,盒子上写着value,打开才能拿到里面的东西。
那reactive创建的响应式对象呢?它本身就是个普通的JavaScript对象(或者数组、Set、Map这类),不需要套value这个快递盒,直接用就能拿到属性或调用方法,unref对它没什么特别大的处理。
Vue3 unref的核心作用到底是啥
现在可以说unref了:它的官方定义很简单,解包一个响应式引用”,但翻译成人话就是——自动帮你拆快递盒,拆完就把东西给你;如果拿到的本来就不是快递盒,直接把东西递过来就行。
举个最直观的例子:假设你写了一个通用的组件,这个组件接收一个props叫count,父组件传过来的时候,可能传了一个普通的数字5,也可能传了一个ref包装的数字ref(5),这时候你在通用组件里用count的话,普通数字直接用没问题,但ref的必须写count.value才能拿到正确的数值,总不能每次用都判断它是不是ref吧?unref就是解决这个麻烦的,不管传的是啥,你写unref(count),它都会返回5这个纯数字。
什么时候必须用unref?什么时候没必要?
很多开发者会纠结“到底什么时候该写unref,什么时候直接用.value就行”,其实不用太刻意记,记住这几种场景就够了:
接收外部可能是“普通值”也可能是“响应式引用”的参数
这是unref最常用的场景,尤其是写工具函数、通用组件的时候,比如你要写一个计算商品折扣价的工具函数,这个函数可以在业务组件内部直接调用(这时候传普通价格和折扣率就行),也可能作为props传给另一个通用组件(业务组件传ref过去,通用组件里调用这个函数,或者函数内部要处理响应式更新?不对,处理更新可能要配合watch或者computed,但解包参数一定是unref)。
比如这样的工具函数:
function calculateDiscount(price, discount) {
// 不管price和discount是普通值还是ref,解包成纯数字再计算
const realPrice = unref(price)
const realDiscount = unref(discount)
return realPrice * realDiscount
}
这样不管你是写calculateDiscount(100, 0.8),还是calculateDiscount(ref(200), ref(0.9)),都能拿到正确的结果,工具函数的通用性大大提高。
在模板外快速获取一个可能的响应式值,但不需要后续响应式更新
有时候你只需要拿某个值的“当前快照”,比如点击按钮时打印日志、提交表单时取初始数据做对比,这时候如果这个值可能是ref也可能不是,用unref就很方便,不用判断。
比如提交表单前的对比:
const initialFormData = reactive({ username: '', password: '' })
const currentFormData = reactive({ username: '', password: '' })
// 假设initialFormData某个属性后来可能被改成ref(当然这种情况不多,但通用逻辑里可以留一手)
// 比如你封装了一个通用的表单对比工具,返回初始和当前是否一致
function isFormUnchanged(initial, current) {
// 用Object.keys遍历,每个值都unref一下再对比
return Object.keys(initial).every(key => unref(initial[key]) === unref(current[key]))
}
配合某些Vue3内置工具函数或者第三方库使用时的规范写法
有些Vue3工具函数或者第三方库虽然内部可能已经做了unref,但文档里会建议你显式传入解包后的值,或者显式用unref包装,这样代码的可读性更强,别人一眼就能看出来你传入的可能是响应式引用。
不过也不是所有地方都要用unref,这几种情况就没必要:
确定拿到的100%是ref或者computed
比如你在组件内部自己定义的const count = ref(0),那直接写count.value就好,多写一层unref(count)完全是多余的,反而增加了一点点(几乎可以忽略不计的)函数调用开销。
确定拿到的100%是普通值或者reactive
reactive不用多说,直接访问属性;自己定义的普通变量也不用,unref对它只是原样返回,没有意义。
在Vue3的模板里使用
这个是很多新手容易忽略的点:Vue3的模板编译器已经自动帮你做了ref和computed的解包!也就是说,你在template里写{{ count }},不管count是ref还是普通数字,都会显示正确的数值,不需要加.value,也不需要加unref。
不过这里有个小坑要注意:如果ref是作为reactive对象的一个属性,那在模板里访问的时候,如果直接通过reactive对象访问,会自动解包;但如果把这个ref属性单独赋值给一个变量再传到模板,就不会自动解包了,举个例子:
// 正确
const state = reactive({ count: ref(0) })
// 模板里写{{ state.count }},自动解包成0
// 错误的写法会导致显示[object Object]
const state = reactive({ count: ref(0) })
const tempCount = state.count // 这里tempCount拿到的是ref对象,不是值
// 模板里写{{ tempCount }},不会自动解包,会显示对象的toString()结果
// 这时候要么在模板里写{{ tempCount.value }},要么在JS里用unref(tempCount)赋值给另一个变量
除了解包,unref还有没有其他隐藏功能?
unref没有什么“隐藏功能”,它的实现逻辑非常简单——内部就是判断一下传入的参数是不是ref类型,如果是,就返回.value,否则直接返回参数,你甚至可以自己手写一个简化版的unref:
function myUnref(val) {
return isRef(val) ? val.value : val
}
不过这里要提一下isRef,它是Vue3内置的另一个工具函数,用来判断一个值是不是ref类型,虽然unref内部已经用了isRef,但有时候你还是需要单独用isRef做判断的,比如当你不仅需要拿到值,还需要根据它是不是ref来做不同的处理(比如如果是ref就监听它的变化,不是的话就不用)。
Vue3 unref和toValue有什么区别?
很多开发者可能已经接触过Vue3.3+新增的toValue函数,它看起来和unref很像,那它们到底有什么不一样?
其实在Vue3.4之前,toValue确实和unref的功能几乎完全一样,但从Vue3.4开始,toValue新增了一个功能:如果传入的是一个函数,它会调用这个函数并返回结果,这个功能主要是配合Vue3的“响应式函数”或者“延迟加载响应式值”的场景使用的,
// 假设你有一个延迟获取响应式数据的函数
function getCount() {
return ref(0)
}
// Vue3.3+用toValue可以直接拿到值
const realCount = toValue(getCount) // 先调用getCount拿到ref,再解包成0
// 但unref不行,它只会原样返回getCount这个函数
const realCount2 = unref(getCount) // 拿到的是function getCount() { ... }
不过如果你的项目还在用Vue3.3以下的版本,就不用担心这个问题,toValue和unref是一样的。
Vue3 unref的使用原则
最后再总结一下,避免踩雷的几个小原则:
- 通用场景用unref:写工具函数、通用组件时,不管外部传的是普通值还是ref,都用unref解包。
- 确定场景直接用:自己定义的100%是ref就用.value,100%是普通值或reactive就直接访问。
- 模板里别瞎用:除非是把reactive里的ref属性单独赋值出来了,否则模板里不用加unref或.value。
- Vue3.4+区分toValue:如果可能传入函数,就用toValue;否则继续用unref也没问题。
现在你应该对Vue3 unref有了清晰的认识了吧?其实它就是一个很简单的“工具人”,帮你省掉判断ref的麻烦,别把它想得太复杂,多用几次就熟练了。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网

