基本模板引用
虽然 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>
- 可以通过类似于MDN HTML接口类型 的页面来获取正确的 DOM 接口。
值得注意:
注意为了严格的类型安全,有必要在访问 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"></Box>
</template>
<script setup lang="ts" name="RootApp">
import Box from "/@/views/box/index.vue"
import { ref } from "vue";
import type { ComponentPublicInstance } from 'vue'
let dom = ref<ComponentPublicInstance | null>(null)
</script>
ComponentPublicInstance 共享类型示例:(部分)
🚵♂️ 博主座右铭:向阳而生,我还在路上!
——————————————————————————————
🚴博主想说:将持续性为社区输出自己的资源,同时也见证自己的进步!
——————————————————————————————
🤼♂️ 如果都看到这了,博主希望留下你的足迹!【📂收藏!👍点赞!✍️评论!】
——————————————————————————————