你不知道的 Vue3 中获取 DOM 节点或组件实例还能这么玩

本文详细介绍了Vue3中如何使用templatereference进行DOM操作,包括字符串、函数和组件引用的区别,以及在v-for循环中的应用。特别关注了类型安全,如如何为模板引用标注类型和处理组件实例的类型获取。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

在这里插入图片描述

vue3.5中获取元素组件

在最新发布的Vue3.5版本中,提供了一个新的获取元素节点和组件实例的Api : useTemplateRef()。这是一个全新的Api方式。详情查看官方文档

<script setup>
import { useTemplateRef, onMounted } from 'vue'

const inputRef = useTemplateRef('input')

onMounted(() => {
  console.log(inputRef.value); //input
   
})
</script>

<template>
  <input ref="input" />
</template>

推荐大家在vue3.5版本中使用 useTemplateRef(),对于在使用 Vue3.5版本之前的伙伴,依然推荐使用以下的案例方式来获取元素或组件节点实例。


基本模板引用

虽然 Vue 的声明性渲染模型为你抽象了大部分对 DOM 的直接操作,但在某些情况下,我们仍然需要直接访问底层 DOM 元素。 而在Vue 环境中通过原生的 document 去获取会存在一些问题,所以在 Vue3 中我们通过在标签节点上定义一个 ref 属性来得到我们想要的节点。

  • 类型:string | Function
  • ref :用于注册标签元素子组件 的引用对象。

如果使用的是选项式 API,引用将被注册在组件的 this.$refs 对象里:

<p ref="p">hello</p>

如果使用的是组合式 API,引用将存储在与名字匹配的 ref 数据变量里:

<template>
  <p ref="p">hello</p>
</template>

<script setup>
import { ref } from 'vue'

// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const p = ref()
</script>


如果不使用 <script setup> 语法糖,需确保从 setup()函数 返回 ref

export default {
  setup() {
    const p = ref(null)
    // ...
    return {
      p
    }
  }
}

可以直接拿到 DOM 标签节点
在这里插入图片描述

注意

你只能在组件挂载完成后 (onMounted()函数成功调用后)才能访问模板引用。如果你想在模板中的表达式上访问该 DOM 元素,在初次渲染时会是 null。这是因为在初次渲染前这个时间阶段元素还未创建且不存在呢

  • 如果你需要侦听一个模板引用 ref 的变化,确保考虑到其值会为 null 的情况:
watchEffect(() => {
  if (p.value) {
      console.log(p.value);
  } else {
    // 此时还未挂载,或此元素已经被卸载(例如通过 v-if 控制)
  }
})

v-for 中的模板引用

  • 需要 Vue3 的 3.2.25 及以上版本才能支持
  • 当在 v-for 中使用模板引用获取DOM元素时,对应的 ref 中包含的值是一个数组,它将在元素被挂载后包含对应整个列表的所有标签元素:
<template>
  <ul>
    <li v-for="item in list" ref="itemRefs">
      {{ item }}
    </li>
  </ul>
</template>

<script setup>
import { ref, onMounted } from 'vue'
const list = ref([1, 2, 3])
const itemRefs = ref([])
onMounted(() => console.log(itemRefs.value))
</script>

在这里插入图片描述

  • 应该注意的是,ref 数组并不保证与源数组相同的顺序。

函数模板引用

推荐使用函数模板引用的方式)灵活性最高!也是作者我喜欢用的方式

除了使用字符串值作名字,ref 还可以绑定为一个函数方法,会在每次组件更新时都被调用。该函数会收到元素引用作为其第一个参数

<template>
    <input :ref="dok">
</template>
<script setup lang="ts" name="Box">
import { ref } from "vue";
let b = ref<HTMLInputElement | null>(null)
const dok = (el: HTMLInputElement): undefined => {
    console.log(el);
    b.value = el;//得到元素标签节点 ,并赋值给 b 属性保存起来。
}
</script>
  • 注意我们这里需要使用动态的 :ref 绑定才能够传入一个函数。当绑定的元素被卸载时,函数也会被调用一次,此时的 el 参数会是 null。你当然也可以绑定一个内联函数或函数方法,这是可选的。

组件模板引用

  • ref如果用于普通 DOM 元素,引用将是元素本身;如果用于子组件,引用得到的值将是子组件的实例对象
<template>
    <Box ref="dom"></Box>
</template>

<script setup lang="ts" name="RootApp">
import Box from "/@/views/box/index.vue"
import { ref, onMounted } from "vue";

let dom = ref<InstanceType<typeof Box> | null>(null)

onMounted(() => {
    console.log(dom.value);
})

组件实例对象
在这里插入图片描述

组件引用说明

如果一个子组件使用的是选项式 API 或没有使用 <script setup> 语法糖,被引用的组件实例和该子组件的 this 完全一致,这意味着父组件对子组件的每一个属性和方法都有完全的访问权。这使得在父组件和子组件之间创建紧密耦合的实现细节变得很容易,当然也因此,应该只在绝对需要时才使用组件引用。大多数情况下,你应该首先使用标准的 props 和 emit 接口来实现父子组件交互

  • 对使用了 <script setup> 语法糖 的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup> 语法糖 的子组件中的任何声明的绑定,除非子组件在其中通过 defineExpose 宏函数显式的暴露要公开的属性值:
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

// 像 defineExpose 这样的编译器宏不需要导入
defineExpose({
  a,
  b
})
</script>

在这里插入图片描述

  • 当父组件通过模板引用获取到了该组件的实例时,得到的实例类型为 { a: number, b: number } (ref 都会自动解包,和一般的实例一样)。

在父组件加载完成后,就可以读取到子组件实例上暴露的值。

onMounted(() => {
    console.log(dom.value);
    console.log(dom.value.a);  //1
    console.log(dom.value.b);  //2
})

为模板引用标注类型

  • 模板引用需要通过一个显式指定的泛型参数和一个初始值 null 来创建:

例:

<template>
  <input ref="el" />
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'

const el = ref<HTMLInputElement | null>(null)

onMounted(() => {
  el.value?.focus()
})
</script>


值得注意:

注意为了严格的类型安全,有必要在访问 el.value 时使用可选链或类型守卫。这是因为直到组件被挂载前,这个 ref 的值都是初始的 null,并且在由于 v-if 的行为将引用的元素卸载时也可以被设置为 null


为组件模板引用标注类型

  • 为了获取 组件Box 的类型,我们首先需要通过 typeof 得到其类型,再使用 TypeScript 内置InstanceType 工具类型来获取其实例类型:

<template>
    <Box ref="dom"></Box>
</template>
<script setup lang="ts" name="RootApp">
import Box from "/@/views/box/index.vue"
import { ref } from "vue";
let dom = ref<InstanceType<typeof Box> | null>(null)

</script>
  • 如果组件的具体类型无法获得,或者你并不关心组件的具体类型,那么可以使用 ComponentPublicInstance。这只会包含所有组件都共享的属性。
<template>
    <Box ref="dom">