Vue ref 属性在不同环境下的行为差异?

<span :="refApi">区域</span>
<span ref="ref1">位置</span>
<span :="{ ref: 'ref2' }">操作</span>

const ref1 = ref();
const ref2 = ref();
const ref3 = ref();
const refApi = {
  ref: 'ref3'
};
console.log(ref1, ref2, ref3);

在开发环境下 ref1, ref2, ref3 皆符合预期指向 span
但是打包后的生产环境就只有 ref1 正常指向 span , ref2, ref3 皆为 undefined
为什么表现不一致?

阅读 1.2k
avatarAI BotBETA

在 Vue 3 中,ref 是一个函数,它返回一个响应式引用。这个引用的值是你在调用 ref 函数时传入的参数。在你的代码中,ref1ref2ref3 都被初始化为对 span 元素的引用。

但是,当你尝试将 ref 作为一个对象的属性时(如 refApi),情况就有些不同了。在这种情况下,refApi.ref 是一个字符串,而不是一个对 span 元素的引用。因此,当你尝试访问 refApi.ref 时,你实际上得到的是一个字符串,而不是一个对 span 元素的引用。

所以,当你试图打印 ref1ref2ref3 时,你实际上是在打印对 span 元素的引用。在开发环境中,这些引用是有效的,因为你的代码正在运行时,这些元素是存在的。但在生产环境中,由于代码已经被打包和优化,这些引用可能已经失效了,因为元素可能已经被移除或更改了。

这就是为什么在生产环境中,你只能看到 ref1 是有效的,而 ref2ref3 都是 undefined 的原因。

2 个回答

因为:="data"简写语法的意思是将data中的每一个属性都当做prop传入,比如data是这么一个对象{name: ref1, age: ref2}等同于 :name="ref1" :age="ref2",注意script中的reftemplate中的ref不是同一个概念,模板中是实例引用,编译的时候会将实例绑定到对应的data上,然后因为你是用的:=对象绑定,而你绑定的对象里并没有ref(注意这里的ref指的是模版上那个ref),所以2、3实际是没有任何数据绑定的,而ref1因为你是固定的属性绑定,不是动态的,能对应上,也就能绑上了。

这时候你可能会疑惑,:={ref: 'ref2'}以及refApi = {ref: 'ref3'}这不是符合我上述说的吗?
因为你这里ref虽然对应是模版的ref但是你绑定的是字符串,不是某一个ref变量这就好比是ref="'ref2'"【注意有两引号】

知道了以上的内容那么就可以改代码了:const ref3 = ref(); const refApi = {ref: ref3};这么改ref3就有值了
但如果你将模版中的:={ref: ref2}你会发现还是没值,为什么呢?
vueconst r = ref()返回的其实是一个对象,当我们在script中要访问真正的值的时候是要r.value的方式访问的,但是为了方便,在模版里其实我们不需要.valuevue实际会帮我们去做这件事,所以:={ref: ref2}实际上相当于ref="ref2.value",绑定原始值肯定是不行的,所以我们这里需要用到函数的方式来绑定即:={ref: el => ref2 = el}

完整修改如下:

<span :="refApi">区域</span>
<span ref="ref1">位置</span>
<span :="{ ref: el => ref2 = el }">操作</span>

const ref1 = ref();
const ref2 = ref();
const ref3 = ref();
const refApi = {
  ref: ref3
};
console.log(ref1, ref2, ref3);

然后我建议你去VuePlayground看看编译后的代码,有助于你理解我上面所说的

截屏2023-12-20 23.22.39.png

https://cn.vuejs.org/guide/essentials/template-syntax.html#at...
https://cn.vuejs.org/guide/essentials/template-refs.html#func...


由于昨晚太晚了,只回答了为什么生产不行的原因,今天补充说明下为什么开发环境是可以的:
首先,vuescript支持setup属性从而简写一些代码,所以就会存在两种编译结果:

这是没有setupscript
截屏2023-12-21 20.30.28.png

这是有setupscript
截屏2023-12-21 20.31.53.png

关键原因就出在这两种不同结果上,实际上除了昨天我说的那种改法,还有一种改法是改成没有setupscript形式,也就是上面的图1那样,然后你就会发现,不管开发环境还是生产环境(即build之后)都有值了。有没有察觉到什么?没错,在dev模式下,vite编译的结果就是没有setup的结果!!
截屏2023-12-21 19.27.41.png

实际上你的确可以将ref定义成字符串,我们知道在vue实例上其实有这么个属性——refs,它其实就是所有模版中ref的集合,在vue2时代我们都是this.$refs.ref1这么访问的,在vue3中虽然不太需要了,但是实例上依然还是有这么个属性,其实你定义的字符串代表的就是这个refskey
截屏2023-12-21 20.57.02.png

当你绑定的是一个ref对象的时候则会多一步,将这个节点赋值给ref,上图中ref1也存在于refs中;当ref是字符串的时候,除了绑定refsvue还会检查data【vue2叫法,vue3中就是setup() { return data }return出来的对象】中是否有同名的key,有的话也会给该属性绑定:
截屏2023-12-21 21.20.01.png

这也就解释了为什么改成无setupscript版本就可以了。有的同学可能会疑惑那为什么<script setup>的版本不行呢?不也是定义了同样的变量名const ref2 ref3吗?这是因为它们只是变量,build后是通过闭包访问的,并不是vue实例上的state
截屏2023-12-21 21.14.45.png

从问题和不同编译环境差异两个角度回答

问题

按我的印象中,不论是 v-bind 还是其缩写 : 都需要对应一个属性吧(还是最新的版本发生了变化?)

如果题主是期望实现动态属性,那也应该这么做:

<span :[key]="refApi"></span>

而不是

<span :="refApi"></span>

而且根据官方文档的描述,ref 理论取值就只有 string | Function

所以理论正确的写法应该是

<template>
    <span :ref="refApi">区域</span>
    <span ref="ref1">位置</span>
</template>

<script>
import { ref, onMounted } from 'vue'

const ref1 = ref();
const ref2 = ref();
const refApi = (e) => {
  ref2.value = e
};

// 针对 ref 的操作建议在挂载后访问
onMounted(() => {
  console.log(ref1.value, ref2.value);
})
</script>

不同编译环境的差异

至于为什么会出现开发环境能够正常捕获到,但是生产环境失败,个人认为是生产环境下,脚手架或编译工具进行了优化导致的,具体原因恐怕得发个 issue 问官方团队了(如果后续有蹲到具体的产生原因,也可以 @ 我一下)

总之,上方的写法是符合规范的,能够保证编译后在生产环境也正常运行

参考

vue官方文档中关于ref属性的描述
vue官方discord (discord看运气,偶尔不挂梯也能访问)
vue github discussions
vue github issue
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏