Vue 3 中模板 ref 的声明方式

FreeGuideOnline 最新 2026-07-07

虽然这种方式仍然可用,但Vue 3更推荐使用组合式API,它能提供更好的类型推断和逻辑复用能力。

### 组合式 API 中声明模板 ref

组合式API要求你显式声明一个与模板中 `ref` 属性同名的响应式引用。这种引用由 `ref()` 函数创建,初始值通常设为 `null`。

```html
<template>
  <input ref="myInput" />
</template>

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

const myInput = ref(null)

onMounted(() => {
  myInput.value.focus() // 注意 .value
})
</script>

声明步骤解析

  1. 导入并调用 ref() :创建一个值为 null 的响应式引用。
  2. 在模板中使用 ref 属性:将属性值设为与响应式引用相同的名称。
  3. 在生命周期钩子中访问:组件挂载后,ref 对象的值会被自动赋值为对应的DOM元素或子组件实例。注意,在 <script setup>setup() 函数中访问时需通过 .value

在模板中绑定 ref 属性

ref 属性的值必须是一个字符串常量,或者是绑定一个动态名称(使用 :ref)。当使用字符串常量时,它必须与 setup 中声明的引用变量名完全相同。Vue 会在挂载时自动完成赋值。

<!-- 静态 ref:引用名称必须与变量 myInput 匹配 -->
<input ref="myInput" />

<!-- 动态 ref:绑定一个字符串变量 -->
<input :ref="elementName" />

对于动态 ref,你需要提前声明一个响应式变量来存储将要绑定的字符串名称:

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

const elementName = ref('myInput')
// 引用的响应式变量也要命名为 myInput
const myInput = ref(null)
</script>

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

注意:动态 ref 绑定的字符串值改变时,旧的引用会被置为 null,新的引用会被重新赋值,这通常用于条件渲染或循环场景。

在 setup 中访问模板 ref

无论是 <script setup> 还是普通的 setup() 函数,你只能在组件挂载之后访问模板 ref。挂载前,ref.valuenull。最常用的方式是在 onMounted 钩子内执行依赖DOM的操作。

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

const divRef = ref(null)

onMounted(() => {
  console.log(divRef.value) // <div>...</div>
})

// 错误:此时为 null
console.log(divRef.value)
</script>

<template>
  <div ref="divRef">内容</div>
</template>

如果需要在更新后响应式地操作DOM,可以结合 watchEffectnextTick,但这些仍然要求元素已经存在。例如,在 v-if 条件变更后,你可以使用 nextTick 等待DOM更新。

<script setup>
import { ref, nextTick, watchEffect } from 'vue'

const show = ref(false)
const box = ref(null)

watchEffect(async () => {
  if (show.value) {
    await nextTick()
    box.value?.scrollIntoView()
  }
})
</script>

使用 TypeScript 标注模板 ref 的类型

为模板 ref 添加类型标注可以获得更好的编辑器提示和编译时检查。根据引用目标的不同,类型声明略有差别。

引用原生DOM元素

直接使用泛型指定对应的DOM元素类型:

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

const inputRef = ref<HTMLInputElement | null>(null)
</script>

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

Vue 会自动将解析到的DOM元素赋值进去,泛型保证了 .value 的类型为 HTMLInputElement | null

引用子组件实例

如果引用的是Vue组件,需要导入组件的实例类型。Vue 3 提供了 ComponentPublicInstance 类型,或者直接从组件中导出类型。

<script setup lang="ts">
import { ref } from 'vue'
import MyModal from './MyModal.vue'

// 如果子组件使用了 defineExpose,则暴露的属性和方法在此类型上可用
const modalRef = ref<InstanceType<typeof MyModal> | null>(null)

onMounted(() => {
  modalRef.value?.open() // open 方法由 defineExpose 暴露
})
</script>

<template>
  <MyModal ref="modalRef" />
</template>

注意:只有子组件通过 defineExpose 显式暴露的成员,才能在父组件的 ref 中访问。如果需要自定义类型,可以单独声明接口并强制转换。

动态 ref 绑定与条件判断

除了前面提到的 :ref 动态名称,还可以通过计算属性或函数动态决定 ref 的绑定。但更常见的是在 v-for 中处理多个 ref。

在 v-for 中使用 ref

当在 v-for 循环中使用 ref 时,一个 ref 变量会被填充为一个包含所有对应DOM节点或组件实例的数组。

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

const listRef = ref([]) // 注意:初始为空数组

onMounted(() => {
  // listRef.value 是一个数组,包含所有 <li> 元素
  listRef.value.forEach(el => console.log(el))
})
</script>

<template>
  <ul>
    <li v-for="item in 5" :key="item" ref="listRef">{{ item }}</li>
  </ul>
</template>

这种方式获取到的数组顺序与渲染顺序一致。如果数据项变化,数组也会自动更新(新增或移除对应的DOM引用)。

动态 ref 数组的 TypeScript 类型

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

const listRef = ref<HTMLElement[]>([])
</script>

注意事项与最佳实践

  1. 命名一致性:模板中的静态 ref="foo" 必须与 <script> 中声明的 const foo = ref(null) 名称完全一致。这是Vue编译器通过编译时分析实现的自动绑定。
  2. 只读的 ref:你不能手动修改模板 ref 的 .value 来替换DOM元素。它的值由Vue内部管理,直接修改可能导致不一致。
  3. 初始值为 null:始终为模板 ref 初始化为 null,因为元素在挂载前不存在。
  4. 访问时机:如需在 setup 中同步获取元素引用(不推荐),可以使用 nextTickwatch 结合 v-if,但最佳实践是使用 onMounted 或事件处理函数。
  5. 子组件暴露:如果希望父组件通过模板 ref 访问子组件的方法或属性,请确保子组件使用 defineExpose 明确暴露,例如:
    // 子组件
    defineExpose({ open, count })