Vue3迁移问题:函数式ref替换数组$refs时出现重复项
Vue3 中可靠获取动态子组件 Refs 列表的方案
问题场景
现有Season组件包含动态数量的Episode子组件,每个Episode通过监听器和计算属性维护自身状态,父组件需要聚合这些子组件的计算属性生成汇总面板。
在Vue2中直接通过this.$refs.episodes遍历子组件实例即可完成聚合,但Vue3官方推荐的函数式ref方案(结合beforeUpdate重置数组)在列表重排、添加/删除子项时,refs数组会出现重复项,导致聚合结果错误。不想用自定义事件总线,也无法接受大规模重构,需要贴近Vue2的简洁实现方式。
解决方案
方案1:用Set自动去重(改动最小)
利用Set数据结构自动去重的特性,替代数组存储refs,确保每次更新后只保留当前渲染的子组件实例:
export default { data() { return { itemRefs: new Set() } }, methods: { setItemRef(el) { if (el) { this.itemRefs.add(el) } } }, beforeUpdate() { this.itemRefs.clear() }, computed: { summary() { // 将Set转为数组后执行聚合逻辑 const episodes = Array.from(this.itemRefs) return episodes.reduce((total, episode) => { // 替换为你的具体聚合逻辑,比如累加子组件的计算属性值 return total + episode.episodeStatusScore }, 0) } } }
模板部分保持官方推荐的写法即可:
<Episode v-for="item in episodesList" :ref="setItemRef" />
原理:Set的add方法会自动忽略重复的组件实例,beforeUpdate清空Set后,每次更新都会重新收集当前渲染的子组件,从根源避免重复项。
方案2:通过唯一Key关联Refs(更精准)
如果每个Episode有唯一标识(比如id),可以用对象存储refs,以唯一id为键,确保每个子组件实例对应唯一条目,同时处理组件销毁时的清理:
<Episode v-for="episode in episodesList" :key="episode.id" :ref="el => setItemRef(el, episode.id)" />
export default { data() { return { itemRefMap: {} } }, methods: { setItemRef(el, episodeId) { if (el) { this.itemRefMap[episodeId] = el } else { // 组件销毁时会传入null,自动删除对应条目 delete this.itemRefMap[episodeId] } } }, computed: { summary() { const episodes = Object.values(this.itemRefMap) return episodes.reduce((total, episode) => { return total + episode.episodeStatusScore }, 0) } } }
原理:通过唯一id绑定子组件与refs的对应关系,增删重排时都会精准更新,无需手动清空数组,组件销毁时自动清理无效条目,完全避免重复问题。
说明
两种方案都无需大规模重构原有代码,方案1改动最小,适合快速修复;方案2更稳定精准,适合有唯一标识的场景,都能实现类似Vue2中$refs数组的可靠效果。
内容的提问来源于stack exchange,提问作者CodeMoose
相关产品推荐
相关产品推荐

