10Vue3 模板引用 (Template Refs)

概念

模板引用是 Vue 3 中直接访问 DOM 元素或组件实例的方式。通过特殊的 ref 属性,可以在 JavaScript 中获取对模板中元素的直接引用。

基本用法

1. 创建和使用 ref

<template>
  <div>
    <!-- 在元素上使用 ref -->
    <input ref="inputRef" type="text" />
    <button @click="focusInput">聚焦输入框</button>
  </div>
</template>

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

// 创建 ref,名称需要与模板中的 ref 属性值一致
const inputRef = ref(null)

const focusInput = () => {
  // 访问 DOM 元素
  inputRef.value.focus()
}

onMounted(() => {
  // 组件挂载后,ref 才会被赋值
  console.log(inputRef.value) // <input type="text">
})
</script>

2. 在选项式 API 中使用

<template>
  <div ref="divRef">这是一个 div 元素</div>
</template>

<script>
export default {
  mounted() {
    // 通过 this.$refs 访问
    console.log(this.$refs.divRef) // <div>这是一个 div 元素</div>
  }
}
</script>

引用组件实例

1. 引用子组件

<!-- 父组件 -->
<template>
  <div>
    <ChildComponent ref="childRef" />
    <button @click="callChildMethod">调用子组件方法</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const childRef = ref(null)

const callChildMethod = () => {
  // 调用子组件的方法
  childRef.value.someMethod()
  
  // 访问子组件的属性
  console.log(childRef.value.someProperty)
}
</script>

<!-- 子组件 ChildComponent.vue -->
<script setup>
import { ref, defineExpose } from 'vue'

const someProperty = ref('子组件数据')

const someMethod = () => {
  console.log('子组件方法被调用')
}

// 需要显式暴露,父组件才能访问
defineExpose({
  someProperty,
  someMethod
})
</script>

动态 ref

<template>
  <div v-for="item in items" :key="item.id">
    <!-- 动态 ref -->
    <div :ref="el => { if (el) divRefs[item.id] = el }">
      {{ item.name }}
    </div>
  </div>
</template>

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

const items = ref([
  { id: 1, name: 'Item 1' },
  { id: 2, name: 'Item 2' }
])

// 使用对象或 Map 存储多个 ref
const divRefs = reactive({})
</script>

ref 函数

<template>
  <div>
    <!-- 使用函数形式的 ref -->
    <input :ref="setInputRef" type="text" />
  </div>
</template>

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

let inputElement = null

const setInputRef = (el) => {
  inputElement = el
}

onBeforeUpdate(() => {
  // 在更新前清除 ref
  inputElement = null
})

onUpdated(() => {
  // 更新后重新设置 ref
  console.log(inputElement)
})
</script>

组合式 API 中的使用模式

1. 使用 ref 数组

<template>
  <div>
    <div 
      v-for="i in 3" 
      :key="i" 
      :ref="el => { if (el) elementRefs[i-1] = el }"
    >
      元素 {{ i }}
    </div>
  </div>
</template>

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

const elementRefs = ref([])

onMounted(() => {
  console.log(elementRefs.value) // [div, div, div]
})
</script>

2. ref 在响应式对象中

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

const state = reactive({
  inputRef: null
})

onMounted(() => {
  // 访问 reactive 对象中的 ref
  if (state.inputRef) {
    state.inputRef.focus()
  }
})
</script>

<template>
  <input ref="el => state.inputRef = el" type="text" />
</template>

最佳实践

  1. 访问时机:ref 在组件挂载后才可用,使用 onMounted 生命周期钩子确保安全访问

  2. 响应式:ref 本身是响应式的,但 ref 指向的 DOM 元素不是响应式的

  3. 类型安全:在 TypeScript 中为 ref 提供类型注解

// TypeScript 示例
import { ref } from 'vue'

// HTML 元素 ref
const inputRef = ref<HTMLInputElement | null>(null)

// 组件 ref(假设子组件有特定类型)
const childRef = ref<InstanceType<typeof ChildComponent> | null>(null)

避免过度使用:优先使用 Vue 的声明式数据驱动,必要时才使用 ref

常见应用场景

  • 聚焦输入框

  • 触发动画

  • 集成第三方 DOM 库

  • 测量元素尺寸

  • 手动触发组件方法

  • 文件上传控件

<template>
  <div>
    <!-- 文件上传示例 -->
    <input 
      ref="fileInput" 
      type="file" 
      style="display: none" 
      @change="handleFileChange"
    />
    <button @click="triggerFileInput">选择文件</button>
  </div>
</template>

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

const fileInput = ref(null)

const triggerFileInput = () => {
  fileInput.value.click()
}

const handleFileChange = (event) => {
  const file = event.target.files[0]
  // 处理文件
}
</script>

模板引用提供了直接操作 DOM 的能力,但在 Vue 应用中应谨慎使用,优先考虑声明式的数据驱动方式

posted @ 2026-02-10 15:27  麻辣~香锅  阅读(4)  评论(0)    收藏  举报