什么是模板引用
模板引用(Template Ref) 是 Vue 提供的一种机制,允许我们在 JavaScript 中直接获取模板中的 DOM 元素 或 组件实例 的引用。
基本语法
<template>
<!-- 在元素/组件上添加 ref 属性 -->
<div ref="myDiv">这是一个div元素</div>
<ChildComponent ref="myChild" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
// 声明与 ref 同名的变量
const myDiv = ref(null) // 用于存储 DOM 元素
const myChild = ref(null) // 用于存储组件实例
onMounted(() => {
// 访问 DOM 元素
console.log(myDiv.value) // <div>这是一个div元素</div>
console.log(myDiv.value.innerText) // "这是一个div元素"
// 访问组件实例
console.log(myChild.value) // ChildComponent 的实例
})
</script>
引用存储的是什么?
// 当 ref 用在普通元素上
<div ref="divRef"></div>
// divRef.value 存储的是: HTMLElement 对象
// 例如:HTMLDivElement, HTMLInputElement, HTMLImageElement 等
// 当 ref 用在组件上
<Child ref="childRef"></Child>
// childRef.value 存储的是: 组件的实例对象
// 包含组件的所有属性和方法(除非被 defineExpose 限制)
引用赋值的时机
// 生命周期中的 ref 状态
beforeCreate() // ❌ ref 不存在
created() // ❌ ref 不存在(模板尚未编译)
beforeMount() // ❌ ref 不存在(尚未渲染到DOM)
mounted() // ✅ ref 已赋值(DOM渲染完成)
updated() // ✅ ref 更新(DOM更新后)
beforeUnmount() // ✅ ref 仍存在
unmounted() // ❌ ref 被重置为 null
详细使用场景
操作原生 DOM(uni-app不可以)
在 uni-app 环境里<input ref="inputRef">得到的 不是原生 DOM,而是 uni-app 的组件实例,所以inputRef.value.focus()就会报错:focus is not a function。
因此,在 uni-app 里,不要用 ref 操作 DOM,要用 组件属性(例如 focus)控制行为。
但在 uni-app 中,input 实际上是跨平台组件:
<input>
↓
小程序组件 / App组件 / H5组件
所以:ref → 组件实例
而不是:ref → DOM
因此没有:focus()
父组件调用子组件方法
<!-- 子组件 Child.vue -->
<template>
<view>我是子组件</view>
</template>
<script setup>
function sayHello(){
console.log("子组件方法被调用")
}
// 必须暴露方法
defineExpose({
sayHello
})
</script>
<!-- 父组件 -->
<template>
<view>
<Child ref="childRef"/>
<button @click="callChild">调用子组件方法</button>
</view>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const childRef = ref(null)
function callChild(){
childRef.value.sayHello()
}
</script>
Vue 内部的工作原理(简化版)
// Vue 内部的工作原理(简化版)
// 1. 你声明变量
const childRef = ref(null)
// 2. Vue 编译模板时,发现有 ref="childRef"
<template>
<view>
<Child ref="childRef"/>
<button @click="callChild">调用子组件方法</button>
</view>
</template>
// 3. Vue 内部建立映射表
const refRegistry = {
"childRef": childRef // 将 ref 名与你声明的变量关联
}
// 4. 当 DOM 渲染完成后
const element = document.createElement('<view>')
refRegistry["childRef"].value = element // 自动赋值给 .value
// 5. 当组件卸载时
refRegistry["childRef"].value = null // 自动重置为 null
初始值
为什么必须是 null 不是 undefined?
// 为什么不能用 undefined?
const childRef = ref(undefined) // ❌ 技术上可行但不符合规范
// Vue 官方要求使用 null 的原因:
// 1. null 表示"有意的空值"(intentional absence)
// 2. undefined 表示"未定义"(uninitialized)
// 3. null 更适合表示"尚未挂载的引用"