Hello,大家好,我是 Sunday。
2024年9月3日,Vue 3.5 的正式版终于来了。
前几天咱们分享了 Vue 3.5 新特性 其中 useTemplateRef 这个 API 被很多同学所关注。那么这个 API 在源码中究竟是怎么实现的呢?今天咱们就来看一下!
useTemplateRef 的作用
useTemplateRef 是用来专门获取 dom 或者 组件示例 的。
在之前,如果我们想要获取 dom ,那么需要这么做:
- 先为 dom 指定 ref 属性,并且给定一个 value 值
- 在 js 中,声明 value 值的变量,并且给定初始值为 空的 ref
<script setup>
// 首先,您定义了一个值为undefined或空的ref
// 并以您想要的方式命名生成的可用内容
const divEl = ref();
</script>
<template>
<!-- 然后使用与“ref”属性的值相同的名称,在模板中的某个地方 -->
<div ref="divEl" ></div>
</template>
但是,这种方案存在一个问题,那就是:ref 通常用来声明响应式数据。当 ref 不光作为响应式声明,还被作为 dom 实例的时候,那么就难免有点让人疑惑了。
所以在(3.5 之后) Vue 推出了一个新的 API 叫做 useTemplateRef 来解决这个问题:
<template>
<div>
<div ref="el">程序员Sunday</div>
</div>
</template>
<script setup>
import { onMounted, useTemplateRef } from 'vue'
const elRef = useTemplateRef('el')
onMounted(() => {
console.log(elRef.value) // dom 示例
})
</script>
useTemplateRef 的实现原理
useTemplateRef 的实现并不复杂,本质上 依然是基于 ref 的实现,只不过是在 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') 中,代表的就是 "el"
然后是变量,这里主要涉及到两个:
第一个 i:通过 getCurrentInstance() 获取,得到的是 上下文实例。
接下来,通过 i.refs 获取到所有的 ref 数据,然后为 refs 添加 Object.defineProperty 的监听,监听的属性名就是入参 key。如果以 useTemplateRef('el') 为例,那么就是 "el"。
通过监听对应 key 的 get 和 set 标记,这里 重点关注 set 标记,在这里为 r.value 进行了赋值,即:r.value = val。这里的 val 就是 refs[key] 的值,也就是对应的 ref 组件实例。
第二个 r:通过 shallowRef(null) 获取,作为返回值
r 作为 useTemplateRef 的返回值即 最终获取的组件示例。
查看 shallowRef 方法(vue-next-3.5.0-master/packages/reactivity/src/ref.ts),可以看到该方法最终会生成 ref 示例:
图片
同时,在上面我们知道了 r.value 的值,是在触发 refs[key] 的 setter 行为时赋值的,赋值的对象即为 ref 组件实例
因此,当 useTemplateRef 返回 r 时,我们就可以通过 r.value 拿到 ref 组件实例 了
总结
那么到这里,我们就看完了 useTemplateRef 的大致源码。整个 useTemplateRef 源码实现并不复杂,主要逻辑分为两步:
- 通过 Object.defineProperty 监听 ref[key] 的 setter 行为,为 r.value 赋值
- 通过 shallowRef 生成 ref 实例,并作为 useTemplateRef 的返回值