全网首发:Vue 3.5 源码解析,useTemplateRef 实现原理
2024年9月3日,Vue 3.5 的正式版终于发布了。
前几天我们分享了 Vue 3.5 的新特性,其中 useTemplateRef
这个 API 备受关注。那么,它在源码中究竟是如何实现的呢?今天我们就来一探究竟!
useTemplateRef 的作用
useTemplateRef
是用来专门获取 DOM 或组件实例的。
在 Vue 3.5 之前,如果我们想获取 DOM 实例,通常会这样做:
- 为 DOM 指定
ref
属性,并赋予一个值。 - 在 JavaScript 中声明一个
ref
变量,并初始化为空。
<script setup>
// 定义一个 ref,初始值为空
const divEl = ref();
</script>
<template>
<!-- 使用 ref 绑定 DOM -->
<div ref="divEl"></div>
</template>
这种方式有一个问题:ref
通常用于声明响应式数据,而当它同时被用作获取 DOM 实例时,容易让人产生混淆。因此,在 Vue 3.5 之后,推出了 useTemplateRef
API 以解决这个问题:
<template>
<div ref="el">程序员Sunday</div>
</template>
<script setup>
import { onMounted, useTemplateRef } from 'vue';
const elRef = useTemplateRef('el');
onMounted(() => {
console.log(elRef.value); // 获取 DOM 实例
});
</script>
useTemplateRef 的实现原理
useTemplateRef
的实现本质上还是基于 ref
,只是对其进行了封装。我们来看源码中的具体实现。
在 vue-next-3.5.0-master/packages/runtime-core/src/helpers/useTemplateRef.ts
文件下,可以看到 useTemplateRef
的实现逻辑。以下是经过简化后的代码:
export function useTemplateRef(key: Keys) {
const i = getCurrentInstance();
const r = shallowRef(null);
if (i) {
const refs = i.refs === EMPTY_OBJ ? (i.refs = {}) : i.refs;
Object.defineProperty(refs, key, {
enumerable: true,
get: () => r.value,
set: val => (r.value = val),
});
}
return r;
}
代码解析
入参
key
key
代表传入的ref
值,例如在useTemplateRef('el')
中,key
就是'el'
。获取上下文实例
i
使用getCurrentInstance()
获取当前上下文的实例i
,接着通过i.refs
获取所有的ref
数据。定义
refs[key]
的 getter 和 setter
为refs
添加Object.defineProperty
,监听传入的key
值。在set
操作中,会为r.value
赋值,也就是refs[key]
的值(即ref
的组件或 DOM 实例)。返回
r
通过shallowRef(null)
创建一个浅响应的ref
对象r
,并作为useTemplateRef
的返回值。最终,我们可以通过r.value
来获取对应的组件实例或 DOM 实例。
shallowRef 方法解析
shallowRef
是一种浅响应的 ref
,只对其 value
属性进行响应。通过 shallowRef(null)
创建的 r
对象,其 value
属性会在 refs[key]
的 setter 触发时被赋值,即为 ref
对应的 DOM 或组件实例。
总结
useTemplateRef
的源码实现并不复杂,主要包括以下两步:
- 通过
Object.defineProperty
监听refs[key]
的 setter 行为,为r.value
赋值。 - 通过
shallowRef
生成ref
实例,并作为useTemplateRef
的返回值。
通过 useTemplateRef
,我们可以方便地获取 DOM 或组件实例,避免了使用 ref
既声明响应式数据又绑定 DOM 实例带来的困惑。