前几天咱们分享了 Vue 3.5 新特性 其中 useTemplateRef 这个 API 被很多同学所关注。那么这个 API 在源码中究竟是怎么实现的呢?今天咱们就来看一下!

useTemplateRef 的作用

useTemplateRef 是用来专门获取 dom 或者 组件示例 的。

在之前,如果我们想要获取 dom ,那么需要这么做:

  1. 先为 dom 指定 ref 属性,并且给定一个 value 值
  2. 在 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 的实现逻辑

全网首发:Vue3.5 源码解析, 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"

通过监听对应 keygetset 标记,这里 重点关注 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 示例:

全网首发:Vue3.5 源码解析, useTemplateRef 实现原理_API_02

同时,在上面我们知道了 r.value 的值,是在触发 refs[key]setter 行为时赋值的,赋值的对象即为 ref 组件实例

因此,当 useTemplateRef 返回 r 时,我们就可以通过 r.value 拿到 ref 组件实例

总结

那么到这里,我们就看完了 useTemplateRef 的大致源码。整个 useTemplateRef 源码实现并不复杂,主要逻辑分为两步:

  1. 通过 Object.defineProperty 监听 ref[key]setter 行为,为 r.value 赋值
  2. 通过 shallowRef 生成 ref 实例,并作为 useTemplateRef 的返回值