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

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

牛逼!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

推荐文章

liunx服务器监控workerman进程守护
2024-11-18 13:28:44 +0800 CST
使用Ollama部署本地大模型
2024-11-19 10:00:55 +0800 CST
介绍25个常用的正则表达式
2024-11-18 12:43:00 +0800 CST
解决python “No module named pip”
2024-11-18 11:49:18 +0800 CST
js生成器函数
2024-11-18 15:21:08 +0800 CST
Vue3中如何处理路由和导航?
2024-11-18 16:56:14 +0800 CST
JavaScript设计模式:发布订阅模式
2024-11-18 01:52:39 +0800 CST
Vue中的异步更新是如何实现的?
2024-11-18 19:24:29 +0800 CST
Python上下文管理器:with语句
2024-11-19 06:25:31 +0800 CST
在 Docker 中部署 Vue 开发环境
2024-11-18 15:04:41 +0800 CST
Node.js中接入微信支付
2024-11-19 06:28:31 +0800 CST
详解 Nginx 的 `sub_filter` 指令
2024-11-19 02:09:49 +0800 CST
网络数据抓取神器 Pipet
2024-11-19 05:43:20 +0800 CST
nginx反向代理
2024-11-18 20:44:14 +0800 CST
推荐几个前端常用的工具网站
2024-11-19 07:58:08 +0800 CST
go命令行
2024-11-18 18:17:47 +0800 CST
Nginx 负载均衡
2024-11-19 10:03:14 +0800 CST
程序员茄子在线接单