全网首发: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;
}
代码解析
入参
keykey代表传入的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 实例带来的困惑。