Skip to content

$refs 的使用:Vue3 Composition API

问题描述

在 Vue 3 的 Composition API 中,开发者经常遇到无法正确获取 $refs 的问题。当需要在组件挂载后访问特定的 DOM 元素或子组件实例时,传统的 Options API 方式不再适用。

Options API 中的写法:

javascript
mounted() {
  console.log("Mounted - ok");
  console.log(this.$refs.table.temp());
}

但在 Composition API 中直接使用 getCurrentInstance().$refs 会导致错误:

javascript
setup() {
  const that: any = getCurrentInstance();
  onMounted(() => {
    console.log("Mounted - ok");
    console.log(that.$refs.table.temp()); // ERROR: that.$refs is undefined
  });
  return {};
}

解决方案

基础用法:使用 ref()

在 Composition API 中,应该使用 ref() 函数创建引用,并在模板中使用相同的名称:

vue
<template>
  <div ref="table"/>
</template>

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

setup() {
  const table = ref(null);

  onMounted(() => {
    console.log(table.value); // 现在可以正常访问
  });

  return { table };
}
</script>

重要提示

ref 的值在组件挂载完成后才会被填充。在首次渲染期间,table.value 将为 null,因为此时元素还不存在。

访问子组件方法

如果需要访问子组件的方法,需要确保子组件通过 setup() 的返回值暴露这些方法:

子组件代码:

vue
<template>
  <h1>子组件</h1>
</template>

<script>
export default {
  setup(props, ctx) {
    const tempMethod = () => {
      console.log("子组件方法");
    }

    return {
      tempMethod // 暴露方法
    }
  },
}
</script>

父组件代码:

vue
<template>
  <MyCompo ref="table"/>
</template>

<script>
import MyCompo from "@/components/MyCompo.vue"
import { ref, onMounted } from 'vue'

export default {
  components: {
    MyCompo
  },
  
  setup(props, ctx) {
    const table = ref(null);
    
    onMounted(() => {
      table.value.tempMethod() // 正确调用子组件方法
    });
    
    return { table };
  }
}
</script>

处理条件渲染的引用

当引用元素位于条件渲染块中时,需要特别注意:

vue
<template>
  <div v-if="isVisible">
    <div ref="table"/>
  </div>
</template>

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

setup() {
  const table = ref(null);
  const isVisible = ref(false);

  // 监听可见性变化
  watch(isVisible, (newVal) => {
    if (newVal) {
      // 此时 table.value 可用
      console.log(table.value);
    }
  });

  return { table, isVisible };
}
</script>

处理列表中的多个引用

对于 v-for 循环中的多个元素引用,可以使用函数式引用:

vue
<template>
  <div v-for="(item, i) in items"
       :ref="el => elements[i] = el"
       :key="item.id">
    <div>ID: {{ item.id }}</div>
    <div>Name: {{ item.name }}</div>
  </div>
</template>

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

setup() {
  const items = [
    {id: 1, name: "项目1"},
    {id: 2, name: "项目2"},
    {id: 3, name: "项目3"},
  ];
  
  const elements = ref([]);
  
  onMounted(() => {
    console.log(elements.value.map(el => el.textContent));
  });
  
  return { elements, items };
}
</script>

注意

在 Vue 3.2.25 到 3.2.31 版本中,使用 ref="elements" 方式在 v-for 中可能会得到空数组,使用函数式引用可以避免这个问题。

TypeScript 支持

使用 TypeScript 时,可以为引用添加类型注解:

typescript
const table = ref<HTMLDivElement | null>(null);

Vue 3.5+ 推荐方式:useTemplateRef

从 Vue 3.5 开始,官方推荐使用 useTemplateRef 来获取模板引用(需检查具体版本支持情况)。

总结

在 Vue 3 Composition API 中使用 $refs 的关键点:

  1. 使用 ref() 创建引用变量
  2. 在模板中使用相同名称的 ref 属性
  3. 引用值在组件挂载后才可用
  4. 条件渲染的元素需要额外处理
  5. 列表引用推荐使用函数式引用方式
  6. 子组件需要显式暴露方法才能被访问

遵循这些模式,可以避免常见的引用访问问题,并在 Composition API 中正确使用模板引用功能。