You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue.js 3中如何简洁动态管理refs元素的添加与移除?

Vue中简洁动态控制元素ref引用状态的方法

问题场景

你尝试用动态绑定:ref="option.selected? 'foobar': null"来控制元素的ref引用,代码如下:

<script setup lang="ts">
import {ref, useTemplateRef} from "vue";

const options = ref([
  {label: "One"},
  {label: "Two", selected: true},
  {label: "Three"},
]);

const foobar = useTemplateRef('foobar');
</script>

<template>
  <span
    v-for="option in options"
    :key="option.label"
    :ref="option.selected? 'foobar': null"
    @click="option.selected = !option.selected"
  >{{ option.label }}</span>

  Selected elements: {{ foobar }}
</template>

但发现元素一旦加入foobar refs,取消选中后不会被移除;用v-if/v-else拆分元素虽然能解决问题,但会导致大量重复代码,冗余严重。

问题根源

Vue的字符串式动态ref绑定,仅在元素首次渲染或**被重新创建(比如v-if切换)**时才会注册/移除引用。当你把ref动态设为null时,Vue不会主动从已注册的refs集合中删除对应元素的引用,只会忽略后续的更新。

简洁解决方案

方案1:使用函数式ref手动管理引用集合

放弃字符串ref,改用函数形式的ref,手动维护选中元素的引用数组,这是最直接的解决方式:

<script setup lang="ts">
import {ref} from "vue";

const options = ref([
  {label: "One"},
  {label: "Two", selected: true},
  {label: "Three"},
]);

const foobar = ref<HTMLElement[]>([]);

// 手动管理ref的添加与移除
const setFoobarRef = (el: HTMLElement | null, option: typeof options.value[0]) => {
  if (option.selected && el) {
    // 避免重复添加
    if (!foobar.value.includes(el)) {
      foobar.value.push(el);
    }
  } else if (el) {
    // 移除对应元素的引用
    foobar.value = foobar.value.filter(item => item !== el);
  }
};
</script>

<template>
  <span
    v-for="option in options"
    :key="option.label"
    :ref="(el) => setFoobarRef(el, option)"
    @click="option.selected = !option.selected"
  >{{ option.label }}</span>

  Selected elements: {{ foobar }}
</template>

这种方式完全由你控制ref的生命周期,选中状态切换时能实时更新foobar数组,不需要重复写元素代码。

方案2:封装子组件消除代码冗余

如果你的元素结构复杂,重复代码多,可以把可点击的选项封装成独立子组件,内部处理ref的条件逻辑:

<!-- OptionItem.vue -->
<script setup lang="ts">
import {ref, watch} from "vue";
const props = defineProps<{
  option: {label: string; selected: boolean}
}>();
const emit = defineEmits<{
  'update:option': [option: typeof props.option]
}>();

const elRef = ref<HTMLElement | null>(null);
const foobarRef = defineRef<HTMLElement | null>();

// 监听选中状态,控制ref的赋值
watch(() => props.option.selected, (isSelected) => {
  foobarRef.value = isSelected ? elRef.value : null;
});

const toggleSelected = () => {
  emit('update:option', {...props.option, selected: !props.option.selected});
};
</script>

<template>
  <span ref="elRef" @click="toggleSelected">{{ props.option.label }}</span>
</template>

父组件使用时就非常简洁:

<!-- Parent.vue -->
<script setup lang="ts">
import {ref, useTemplateRef} from "vue";
import OptionItem from "./OptionItem.vue";

const options = ref([
  {label: "One"},
  {label: "Two", selected: true},
  {label: "Three"},
]);

const foobar = useTemplateRef('foobar');
</script>

<template>
  <OptionItem
    v-for="option in options"
    :key="option.label"
    :option="option"
    @update:option="(newOption) => options.value = options.value.map(opt => opt.label === newOption.label ? newOption : opt)"
    :ref="option.selected ? 'foobar' : null"
  />

  Selected elements: {{ foobar }}
</template>

通过组件封装,把重复逻辑收拢到子组件,父组件只需要处理数据传递,既解决了ref更新问题,又消除了代码冗余。

方案3:结合watch手动同步refs(备选)

如果你坚持用字符串ref,可以通过watch监听options的选中状态变化,手动清理无效的引用:

<script setup lang="ts">
import {ref, useTemplateRef, watch} from "vue";

const options = ref([
  {label: "One"},
  {label: "Two", selected: true},
  {label: "Three"},
]);

const foobar = useTemplateRef<HTMLElement[]>('foobar');

// 监听选中状态变化,清理未选中元素的引用
watch(() => options.value.map(opt => opt.selected), () => {
  if (foobar.value) {
    // 这里需要结合元素的标识(比如dataset)来判断是否仍处于选中状态
    foobar.value = foobar.value.filter(el => {
      const label = el.textContent;
      const option = options.value.find(opt => opt.label === label);
      return option?.selected;
    });
  }
}, {deep: true});
</script>

<template>
  <span
    v-for="option in options"
    :key="option.label"
    :ref="option.selected? 'foobar': null"
    @click="option.selected = !option.selected"
  >{{ option.label }}</span>

  Selected elements: {{ foobar }}
</template>

这种方式需要依赖元素的内容或自定义属性来匹配选项,不如函数式ref直观,适合简单场景应急。

内容的提问来源于stack exchange,提问作者CJ Dennis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 14:01:20