综合 2024年Vue3.5的useTemplateRef让ref操作DOM更加丝滑

2024-11-19 06:37:21 +0800 CST views 2399

牛逼!Vue3.5的useTemplateRef让ref操作DOM更加丝滑

前言

在 Vue 3 中,访问 DOM 和子组件可以通过 ref 进行模板引用,但这个 ref 有时会让人迷惑。比如,定义的 ref 变量到底是一个响应式数据还是 DOM 元素?为什么 templateref 属性的值是一个字符串(例如 ref="inputEl"),却能和 script 中同名的 inputEl 变量绑定在一起?为了解决这些问题,Vue 3.5 推出了 useTemplateRef 函数,使得 ref 的使用更加符合编程直觉。

ref 模版引用的问题

我们先来看一下在 React 中使用 ref 访问 DOM 元素的例子:

const inputEl = useRef<HTMLInputElement>(null);
<input type="text" ref={inputEl} />

useRef 返回的对象是一个 .current 属性,inputEl 通过 ref 属性绑定到 input 元素后,.current 的值就被更新为该 input 元素。这个行为符合编程直觉。

然而,在 Vue 3 中,使用 ref 时的行为却没有这么直观。例如,以下代码中,我们试图像 React 那样在 template 中给 ref 属性绑定一个 ref 变量:

<input type="text" :ref="inputEl" />
const inputEl = ref<HTMLInputElement>();

这么写不但不会报错,但访问 inputEl 时总是 undefined。原因是 ref 属性接受的并不是 ref 变量,而是其名称。正确的代码应该是这样的:

<input type="text" ref="inputEl" />
const inputEl = ref<HTMLInputElement>();

如果我们将 ref 模版引用的逻辑抽取为 hooks,情况更加复杂。即使 Vue 组件中不使用该变量,我们仍需要在组件中导入该 ref 变量。这看起来很奇怪。

useTemplateRef 函数

为了解决这些问题,Vue 3.5 引入了 useTemplateRef 函数。这个函数的用法非常简单:它接收一个参数 key(字符串),返回一个 ref 变量。这个 key 值与 templateref 属性的值相同。返回的 ref 变量指向 DOM 元素或子组件。

示例代码如下:

<template>
  <input type="text" ref="inputRef" />
  <button @click="setInputValue">给 input 赋值</button>
</template>

<script setup lang="ts">
import { useTemplateRef } from "vue";

const inputEl = useTemplateRef<HTMLInputElement>("inputRef");
function setInputValue() {
  if (inputEl.value) {
    inputEl.value.value = "Hello, world!";
  }
}
</script>

这样,inputEl 作为一个 ref 变量,可以通过 useTemplateRef 函数获取到 input 元素。相比之前的做法,这种方式更符合编程直觉。

hooks 中使用 useTemplateRef

对于 hooks 中的例子,我们可以用 useTemplateRef 来简化代码:

export default function useInput(key) {
  const inputEl = useTemplateRef<HTMLInputElement>(key);
  function setInputValue() {
    if (inputEl.value) {
      inputEl.value.value = "Hello, world!";
    }
  }
  return {
    setInputValue,
  };
}

组件中的代码如下:

<template>
  <input type="text" ref="inputRef" />
  <button @click="setInputValue">给 input 赋值</button>
</template>

<script setup lang="ts">
import useInput from "./useInput";
const { setInputValue } = useInput("inputRef");
</script>

使用 useTemplateRef 后,我们不再需要在组件中导入 inputEl 变量。

动态切换 ref 绑定的变量

有时我们需要根据场景动态切换 ref 绑定的变量,这时 ref 属性的值是动态的。useTemplateRef 同样支持这种场景:

<template>
  <input type="text" :ref="refKey" />
  <button @click="switchRef">切换 ref 绑定的变量</button>
  <button @click="setInputValue">给 input 赋值</button>
</template>

<script setup lang="ts">
import { useTemplateRef, ref } from "vue";

const refKey = ref("inputEl1");
const inputEl1 = useTemplateRef<HTMLInputElement>("inputEl1");
const inputEl2 = useTemplateRef<HTMLInputElement>("inputEl2");
function switchRef() {
  refKey.value = refKey.value === "inputEl1" ? "inputEl2" : "inputEl1";
}
function setInputValue() {
  const curEl = refKey.value === "inputEl1" ? inputEl1 : inputEl2;
  if (curEl.value) {
    curEl.value.value = "Hello, world!";
  }
}
</script>

通过动态切换 refKey,可以改变绑定的 ref 变量。

useTemplateRef 的实现

useTemplateRef 的实现实际上很简单:

function useTemplateRef(key) {
  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;
}

在初始化时,template 中处理 ref 属性时会对 Vue 实例上的 refs 属性进行写操作,并触发 set 拦截器,将 ref 变量的值更新为 DOM 元素或组件实例。

总结

Vue 3.5 中新增的 useTemplateRef 函数解决了使用 ref 时的一些直觉问题,使得模板中的 ref 属性与 script 中的 ref 变量绑定更加合理。此外,它使得 hooks 中的逻辑更加清晰,减少了不必要的变量导入。通过 useTemplateRef,我们可以更方便地在模板中获取 DOM 元素或子组件。

复制全文 生成海报 Vue 前端开发 JavaScript

推荐文章

Vue3 实现页面上下滑动方案
2025-06-28 17:07:57 +0800 CST
用 Rust 构建一个 WebSocket 服务器
2024-11-19 10:08:22 +0800 CST
Gin 与 Layui 分页 HTML 生成工具
2024-11-19 09:20:21 +0800 CST
16.6k+ 开源精准 IP 地址库
2024-11-17 23:14:40 +0800 CST
任务管理工具的HTML
2025-01-20 22:36:11 +0800 CST
为什么要放弃UUID作为MySQL主键?
2024-11-18 23:33:07 +0800 CST
Vue3 结合 Driver.js 实现新手指引
2024-11-18 19:30:14 +0800 CST
Web浏览器的定时器问题思考
2024-11-18 22:19:55 +0800 CST
WebSQL数据库:HTML5的非标准伴侣
2024-11-18 22:44:20 +0800 CST
PHP中获取某个月份的天数
2024-11-18 11:28:47 +0800 CST
在Vue3中实现代码分割和懒加载
2024-11-17 06:18:00 +0800 CST
Golang 几种使用 Channel 的错误姿势
2024-11-19 01:42:18 +0800 CST
向满屏的 Import 语句说再见!
2024-11-18 12:20:51 +0800 CST
PostgreSQL日常运维命令总结分享
2024-11-18 06:58:22 +0800 CST
纯CSS绘制iPhoneX的外观
2024-11-19 06:39:43 +0800 CST
PHP openssl 生成公私钥匙
2024-11-17 05:00:37 +0800 CST
Redis函数在PHP中的使用方法
2024-11-19 04:42:21 +0800 CST
程序员茄子在线接单