Vue 模板引用(ref)全面指南:从基础到高级应用

在 Vue 开发中,虽然声明性渲染模型为我们抽象了大部分 DOM 操作,但在某些场景下,我们仍然需要直接访问底层 DOM 元素或子组件实例。Vue 提供的 ref attribute 正是为解决这类需求而生,它允许我们在元素或组件挂载后获取其直接引用,实现更精细的控制。本文将深入探讨 Vue 模板引用的各种用法与最佳实践。

一、ref 的基本概念与核心作用

ref 是 Vue 中一个特殊的 attribute,类似于 v-for 中的 key,其核心功能是为 DOM 元素或子组件实例创建引用标识。当元素或组件被挂载到 DOM 后,我们可以通过这些引用执行以下操作:

  • 对 DOM 元素进行焦点设置、动画控制等底层操作

  • 初始化或操作第三方库(如 Chart.js 图表实例)

  • 访问子组件的实例方法与属性

  • v-for 循环中批量获取元素引用

关键特性

  • 引用仅在组件挂载后可用(初次渲染前为 null
  • 支持字符串命名与函数绑定两种方式
  • 在组合式 API 中通过 useTemplateRef 函数获取引用
  • 子组件引用的访问受 <script setup> 私有性限制

二、在组合式 API 中使用 ref

2.1 获取 DOM 元素引用

在组合式 API 中,获取 DOM 元素引用的标准流程如下:

xml 复制代码
<script setup>
import { useTemplateRef, onMounted } from 'vue'

// 声明与模板中 ref 值匹配的引用
const inputRef = useTemplateRef('input-element')

// 组件挂载后访问引用
onMounted(() => {
  // 对输入框设置焦点
  inputRef.value.focus()
  
  // 操作 DOM 元素属性
  inputRef.value.placeholder = '已通过 ref 初始化'
})
</script>

<template>
  <input ref="input-element" type="text" />
</template>

2.2 TypeScript 类型推断

Vue 对 TypeScript 提供了良好支持,inputRef.value 的类型会根据匹配的元素自动推断:

xml 复制代码
<script setup lang="ts">
import { useTemplateRef, onMounted } from 'vue'

// TypeScript 自动推断 inputRef.value 为 HTMLInputElement 类型
const inputRef = useTemplateRef('input-element')

onMounted(() => {
  // 类型安全的操作
  inputRef.value.addEventListener('input', (e) => {
    console.log(e.target.value)
  })
})
</script>

三、模板引用的高级使用场景

3.1 监听引用变化

由于引用在组件挂载前为 null,且可能随组件卸载而消失,监听时需进行非空判断:

js

javascript 复制代码
import { useTemplateRef, watchEffect } from 'vue'

const inputRef = useTemplateRef('input-element')

// 响应式监听引用变化
watchEffect(() => {
  if (inputRef.value) {
    // 元素已挂载,执行操作
    inputRef.value.style.border = '2px solid blue'
  } else {
    // 元素未挂载或已卸载
    console.log('元素状态变更,当前为 null')
  }
})

3.2 子组件引用与组件通信

ref 应用于子组件时,引用值为子组件实例,可访问其属性与方法:

xml 复制代码
<!-- 父组件 -->
<script setup>
import { useTemplateRef, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'

// 获取子组件实例引用
const childRef = useTemplateRef('child')

onMounted(() => {
  // 调用子组件方法
  childRef.value.doSomething()
  
  // 访问子组件属性
  console.log(childRef.value.dataValue)
})
</script>

<template>
  <ChildComponent ref="child" />
</template>
xml 复制代码
<!-- 子组件(选项式 API) -->
<script>
export default {
  data() {
    return {
      dataValue: '子组件数据'
    }
  },
  methods: {
    doSomething() {
      console.log('子组件方法被调用')
    }
  }
}
</script>

3.3 <script setup> 组件的引用限制与暴露

使用 <script setup> 的组件默认私有,父组件无法直接访问其属性,需通过 defineExpose 显式暴露:

xml 复制代码
<!-- 子组件(<script setup>) -->
<script setup>
import { ref } from 'vue'

// 私有属性
const privateData = '不会被暴露'
const exposedData = ref('将被暴露的数据')

// 暴露属性与方法
defineExpose({
  exposedData,
  // ref 会自动解包为原始值
  getExposedValue() {
    return exposedData.value
  }
})
</script>

<template>
  <div>子组件内容</div>
</template>
xml 复制代码
<!-- 父组件访问暴露的子组件引用 -->
<script setup>
import { useTemplateRef } from 'vue'
import ExposedChild from './ExposedChild.vue'

const childRef = useTemplateRef('exposedChild')

// 组件挂载后访问暴露的属性
console.log(childRef.value.exposedData) // 输出: "将被暴露的数据"
console.log(childRef.value.getExposedValue()) // 输出: "将被暴露的数据"
</script>

四、v-for 中的模板引用(v3.5+)

Vue 3.5 及以上版本支持在 v-for 中获取元素引用数组,这在批量操作元素时非常实用:

xml 复制代码
<script setup>
import { ref, useTemplateRef, onMounted } from 'vue'

const items = ref([
  { id: 1, name: '项目一' },
  { id: 2, name: '项目二' },
  { id: 3, name: '项目三' }
])

// 获取 v-for 中所有 li 元素的引用数组
const itemRefs = useTemplateRef('listItems')

onMounted(() => {
  // itemRefs.value 是包含所有 li 元素的数组
  console.log('元素数量:', itemRefs.value.length)
  
  // 批量设置样式
  itemRefs.value.forEach((el, index) => {
    el.style.color = index % 2 === 0 ? 'red' : 'blue'
  })
})
</script>

<template>
  <ul>
    <li v-for="item in items" :key="item.id" ref="listItems">
      {{ item.name }}
    </li>
  </ul>
</template>

注意事项

  • ref 数组顺序不一定与源数组完全一致
  • 元素卸载时,对应引用会从数组中移除
  • 可结合 key 提升引用匹配的稳定性

五、函数式模板引用

除了字符串命名,ref 还可绑定为函数,在元素挂载 / 更新 / 卸载时触发:

xml 复制代码
<script setup>
import { ref } from 'vue'

// 声明 ref 变量存储元素引用
const inputEl = ref(null)

// 函数式 ref 绑定
const setInputRef = (el) => {
  // 元素挂载时 el 为 DOM 实例,卸载时为 null
  inputEl.value = el
  
  if (el) {
    // 元素已挂载,初始化操作
    el.placeholder = '函数式 ref 绑定'
  }
}
</script>

<template>
  <!-- 使用 v-bind:ref 绑定函数 -->
  <input v-bind:ref="setInputRef" type="text" />
  
  <!-- 或使用内联函数 -->
  <input :ref="(el) => { if (el) el.focus() }" />
</template>

函数式 ref 的优势

  • 更灵活的引用赋值逻辑
  • 可在元素卸载时执行清理操作
  • 适合动态绑定场景(如条件渲染元素)

六、注意事项

6.1 避免滥用 ref

  • 优先使用声明式编程:Vue 的核心优势在于声明式渲染,应尽量通过数据驱动视图,而非直接操作 DOM
  • 组件通信首选 props/emit:父子组件交互优先使用标准接口,仅在必要时使用 ref 访问子组件
  • 第三方库集成场景:当需要操作库实例(如表单验证库、图表库)时,ref 是合理的选择

6.2 性能考虑

  • 批量操作 DOM :使用 nextTick 确保在 DOM 更新完成后执行批量操作
  • 避免频繁访问 ref :在循环或高频事件中,可先缓存 ref.value 以提升性能
  • v-for 中慎用函数式 ref:函数式 ref 在每次更新时都会被调用,可能影响性能

6.3 类型安全(TypeScript)

  • 显式标注类型 :在复杂场景中,可通过泛型为 useTemplateRef 标注精确类型
  • 使用 defineRefref:确保 ref 变量的类型推导正确
  • 参考官方类型声明:查看 Vue 类型定义文件,了解内置类型的使用方式

七、总结

ref 作为 Vue 中操作底层元素的重要接口,在保持声明式编程优势的同时,为开发者提供了必要的命令式控制能力。从基础的 DOM 焦点设置,到复杂的子组件通信与第三方库集成,ref 的灵活用法贯穿于各类开发场景。

在实际项目中,建议遵循 "声明式优先,命令式保底" 的原则,合理使用 ref 并结合组合式 API 的其他特性(如 watchEffectonMounted),构建既保持 Vue 特性又满足特定需求的应用。

相关推荐
wqq631085515 分钟前
Python基于Vue的实验室管理系统 django flask pycharm
vue.js·python·django
Deng94520131418 分钟前
Vue + Flask 前后端分离项目实战:从零搭建一个完整博客系统
前端·vue.js·flask
Hello.Reader1 小时前
Flink 文件系统通用配置默认文件系统与连接数限制实战
vue.js·flink·npm
EchoEcho3 小时前
深入理解 Vue.js 渲染机制:从声明式到虚拟 DOM 的完整实现
vue.js
C澒3 小时前
Vue 项目渐进式迁移 React:组件库接入与跨框架协同技术方案
前端·vue.js·react.js·架构·系统架构
发现一只大呆瓜4 小时前
虚拟列表:从定高到动态高度的 Vue 3 & React 满分实现
前端·vue.js·react.js
鱼毓屿御5 小时前
如何给用户添加权限
前端·javascript·vue.js
Java新手村5 小时前
基于 Vue 3 + Spring Boot 3 的 AI 面试辅助系统:实时语音识别 + 大模型智能回答
vue.js·人工智能·spring boot
雯0609~5 小时前
hiprint:实现项目部署与打印3-vue版本-独立出模板设计与模板打印页面
前端·vue.js·arcgis
David凉宸6 小时前
Vue 3 + TS + Vite + Pinia vs Vue 2 + JS + Webpack + Vuex:对比分析
javascript·vue.js·webpack