牛逼!Vue3.5的useTemplateRef让ref操作DOM更加丝滑
前言
vue3中想要访问DOM和子组件可以使用ref进行模版引用,但是这个ref有一些让人迷惑的地方。比如定义的ref变量到底是一个响应式数据还是DOM元素?还有template中ref属性的值明明是一个字符串,比如ref="inputEl"
,怎么就和script中同名的inputEl
变量绑到一块了呢?所以Vue3.5推出了一个useTemplateRef
函数,完美的解决了这些问题。
关注公众号:【前端欧阳】,给自己一个进阶vue的机会
ref模版引用的问题
我们先来看一个react
中使用ref访问DOM元素的例子,代码如下:
const inputEl = useRef<HTMLInputElement>(null);
<input type="text" ref={inputEl} />
使用useRef
函数定义了一个名为inputEl
的变量,然后将input元素的ref属性值设置为inputEl
变量,这样就可以通过inputEl
变量访问到input输入框了。
inputEl
因为是一个.current
属性的对象,由于inputEl
变量赋值给了ref属性,所以他的.current
属性的值被更新为了input DOM元素,这个做法很符合编程直觉。
再来看看vue3
中的做法,相比之下就很不符合编程直觉了。
不知道有多少同学和欧阳一样,最开始接触vue3时总是在template中像react
一样给ref属性绑定一个ref变量,而不是ref变量的名称。比如下面这样的代码:
<input type="text" :ref="inputEl" />
const inputEl = ref<HTMLInputElement>();
更加要命的是这样写还不会报错!!!!当我们使用inputEl
变量去访问input输入框时始终拿到的都是undefined
。
经过多次排查发现原来ref属性接收的不是一个ref变量,而是ref变量的名称。正确的代码应该是这样的:
<input type="text" ref="inputEl" />
const inputEl = ref<HTMLInputElement>();
还有就是如果我们将ref模版引用相关的逻辑抽成hooks后,那么必须将在vue组件中也要将ref属性对应的ref变量也定义才可以。
hooks代码如下:
export default function useRef() {
const inputEl = ref<HTMLInputElement>();
function setInputValue() {
if (inputEl.value) {
inputEl.value.value = "Hello, world!";
}
}
return {
inputEl,
setInputValue,
};
}
在hooks中定义了一个名为inputRef
的变量,并且在setInputValue
函数中会通过inputRef
变量对input输入框进行操作。
vue组件代码如下:
<template>
<input type="text" ref="inputEl" />
<button @click="setInputValue">给input赋值</button>
</template>
<script setup lang="ts">
import useInput from "./useInput";
const { setInputValue, inputEl } = useInput();
</script>
虽然在vue组件中我们不会使用inputEl
变量,但是还是需要从hooks中导入useInput
变量。大家不觉得这很奇怪吗?导入了一个变量,又没有显式的去使用这个变量。
如果在这里不去从hooks中导入inputEl
变量,那么inputEl
变量中就不能绑定上input输入框了。
useTemplateRef函数
为了解决上面说的ref模版引用的问题,在Vue3.5中新增了一个useTemplateRef
函数。
useTemplateRef
函数的用法很简单:只接收一个参数key
,是一个字符串。返回值是一个ref变量。
其中参数key字符串的值应该等于template中ref属性的值。
返回值是一个ref变量,变量的值指向模版引用的DOM元素或者子组件。
我们来看个例子,前面的demo改成useTemplateRef
函数后代码如下:
<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>
在template中ref属性的值为字符串"inputRef"
。
在script中使用useTemplateRef
函数,传入的第一个参数也是字符串"inputRef"
。useTemplateRef
函数的返回值就是指向input输入框的ref变量。
由于inputEl
是一个ref变量,所以在click事件中想要访问到DOM元素input输入框就需要使用inputEl.value
。
我们这里是要给输入框中塞一个字符串"Hello, world!",所以使用inputEl.value.value = "Hello, world!"
使用了useTemplateRef
函数后和之前比起来就很符合编程直觉了。template中ref属性值是一个字符串"inputRef"
,使用useTemplateRef
函数时也传入字符串"inputRef"
就能拿到对应的模版引用了。
hooks中使用useTemplateRef
回到前面讲的hooks的例子,使用useTemplateRef
后hooks代码如下:
export default function useInput(key) {
const inputEl = useTemplateRef<HTMLInputElement>(key);
function setInputValue() {
if (inputEl.value) {
inputEl.value.value = "Hello, world!";
}
}
return {
setInputValue,
};
}
现在我们在hooks中就不需要导出变量inputEl
了,因为这个变量只需要在hooks内部使用。
vue组件代码如下:
<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>
由于在vue组件中我们不需要使用inputEl
变量,所以在这里就不需要从useInput
中引入变量inputEl
了。而之前不使用useTemplateRef
的方案中我们就不得不引入inputEl
变量了。
动态切换ref绑定的变量
有的时候我们需要根据不同的场景去动态切换ref模版引用的变量,这时在template中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>
在这个场景template中ref绑定的就是一个变量refKey
,通过点击切换ref绑定的变量
按钮可以切换refKey
的值。相应的,绑定input输入框的变量也会从inputEl1
变量切换成inputEl2
变量。
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;
}
首先使用getCurrentInstance
方法获取当前vue实例对象,赋值给变量i
。
然后调用shallowRef
函数生成一个浅层的ref对象,初始值为null。这个ref对象就是useTemplateRef
函数返回的ref对象。
接着就是判断当前vue实例如果存在就读取实例上面的refs
属性对象,如果实例对象上面没有refs
属性,那么就初始化一个空对象到vue实例对象的refs
属性。
vue实例对象上面的这个refs
属性对象用过vue2的同学应该都很熟悉,里面存的是注册过ref属性的所有 DOM 元素和组件实例。
vue3虽然不像vue2一样将refs
属性对象开放给开发者,但是他的内部依然还是用vue实例上面的refs
属性对象来存储template中使用ref属性注册过的元素和组件实例。
这里使用了Object.defineProperty
方法对refs
属性对象进行拦截,拦截的字段是变量key
的值,而这个key
的值就是template中使用ref属性绑定的值。
以我们上面的demo举例,在template中的代码如下:
<input type="text" ref="inputRef" />
这里使用ref属性在vue实例的refs
属性对象上面注册了一个input输入框,refs.inputRef
的值就是指向DOM元素input输入框。
然后在script中是这样使用useTemplateRef
的:
const inputEl = useTemplateRef<HTMLInputElement>("inputRef")
调用useTemplateRef
函数时传入的是字符串"inputRef"
,在useTemplateRef
函数内部使用Object.defineProperty
方法对refs
属性对象进行拦截,拦截的字段为变量key
的值,也就是调用useTemplateRef
函数传入的字符串"inputRef"
。
初始化时,vue处理input输入框上面的ref="inputRef"
就会执行下面这样的代码:
refs[ref] = value
此时的value
的值就是指向DOM元素input输入框,ref
的值就是字符串"inputRef"
。
那么这行代码就是将DOM元素input输入框赋值给refs
对象上面的inputRef
属性上。
由于这里对refs
对象上面的inputRef
属性进行写操作,所以会走到useTemplateRef
函数中Object.defineProperty
定义的set
拦截。代码如下:
const r = shallowRef(null);
Object.defineProperty(refs, key, {
enumerable: true,
get: () => r.value,
set: (val) => (r.value = val),
});
在set
拦截中会将DOM元素input输入框赋值给ref变量r
,而这个r
就是useTemplateRef
函数返回的ref变量。
同样的当对象refs
对象的inputRef
属性进行读操作时,也会走到这里的get
拦截中,返回useTemplateRef
函数中定义的ref变量r
的值。
总结
Vue3.5中新增的useTemplateRef
函数解决了ref属性中存在的几个问题:
不符合编程直觉,template中ref属性的值是script中对应的ref变量的变量名。
在script中如果不使用ts,则不能直观的知道一个ref变量到底是响应式数据还是DOM元素?
将定义和访问DOM元素相关的逻辑抽到hooks中后,虽然vue组件中不会使用到存放DOM元素的变量,但是也必须在组件中从hooks中导入。
接着我们讲了useTemplateRef
函数的实现。在useTemplateRef
函数中会定义一个ref对象,在useTemplateRef
函数最后就是return返回这个ref对象。
接着使用Object.defineProperty
对vue实例上面的refs
属性对象进行get和set拦截。
初始化时,处理template中的ref属性,会对vue实例上面的refs
属性对象进行写操作。
然后就会被set拦截,在set拦截中会将useTemplateRef
函数中定义的ref对象的值赋值为绑定的DOM元素或者组件实例。
而useTemplateRef
函数就是将这个ref对象进行return返回,所以我们可以通过useTemplateRef
函数的返回值拿到template中ref属性绑定的DOM元素或者组件实例。
关注公众号:【前端欧阳】,给自己一个进阶vue的机会
另外欧阳写了一本开源电子书vue3编译原理揭秘,看完这本书可以让你对vue编译的认知有质的提升。这本书初、中级前端能看懂,完全免费,只求一个star。
牛逼!Vue3.5的useTemplateRef让ref操作DOM更加丝滑的更多相关文章
- 【React自制全家桶】三、React使用ref操作DOM与setState遇到的问题
在React中同时使用ref操作DOM与setState常常会遇到 比如操作的DOM是setState更新之前的DOM内容,与想要的操作不一致.导致这样的原因是setState函数是异步函数. 就是当 ...
- 在-for 循环里面如何利用ref 操作dom
由于dom 元素是在渲染之后才能操作,所以如果想取到dom元素,要放到mounted()这个生命周期函数里面,并且还要用this.$nextTick(function () {})
- 为什么我会认为SAP是世界上最好用最牛逼的ERP系统,没有之一?
为什么我认为SAP是世界上最好用最牛逼的ERP系统,没有之一?玩过QAD.Tiptop.用友等产品,深深觉得SAP是贵的有道理! 一套好的ERP系统,不仅能够最大程度承接适配企业的管理和业务流程,在技 ...
- 我喜欢ASP.NET的MVC因为它牛逼的9大理由(转载)
我很早就关注ASP.NET的mvc的,因为最开始是学了Java的MVC,由于工作的原因一直在做.Net开发,最近的几个新项目我采用了MVC做了,我个一直都非常喜欢.Net的MVC.我们为什么使用MVC ...
- 最牛逼android上的图表库MpChart(三) 条形图
最牛逼android上的图表库MpChart三 条形图 BarChart条形图介绍 BarChart条形图实例 BarChart效果 最牛逼android上的图表库MpChart(三) 条形图 最近工 ...
- 最牛逼android上的图表库MpChart(二) 折线图
最牛逼android上的图表库MpChart二 折线图 MpChart折线图介绍 MpChart折线图实例 MpChart效果 最牛逼android上的图表库MpChart(二) 折线图 最近工作中, ...
- 最牛逼android上的图表库MpChart(一) 介绍篇
最牛逼android上的图表库MpChart一 介绍篇 MpChart优点 MpChart是什么 MpChart支持哪些图表 MpChart效果如何 最牛逼android上的图表库MpChart(一) ...
- .Net免费公开课视频+资料+源码+经典牛逼 汇总篇【持续更新】
博主推荐一:WP8.1最经典培训教程 博主点评:经典Windows Phone8.1 Runtime API培训最经典教程,此教程由传智播客蒋坤老师录制的一整套WP8.1入门级视频教程,讲授内容非常广 ...
- 科学家有了钱以后,真是挺吓人的——D.E.Shaw的牛逼人生
科学家有了钱以后,真是挺吓人的——D.E.Shaw的牛逼人生 黑科技,还是要提D.E.Shaw Research这个奇异的存在. 要讲这个黑科技,我们可能要扯远一点,先讲讲D.E. Shaw这个人是怎 ...
- cssViewer牛逼的chrome插件
很牛逼,功能很强大.
随机推荐
- 日常工作中需要避免的9个React坏习惯
前言 React是前端开发领域中最受欢迎的JavaScript库之一,但有时候在编写React应用程序时,可能陷入一些不佳的习惯和错误做法.这些不佳的习惯可能导致性能下降.代码难以维护,以及其他问题. ...
- 推荐一枚宝藏Up主,顺便聊聊感想
众所周知,B站是学习网站 最近发现一宝藏Up主,主要做科普,主题包括但不限于:大模型的底层算法.量子计算底层原理和硬件设计,以及其他物理或者自然科学主题,总体偏向于理工科. 值得推荐的理由:Up主对底 ...
- tp5命名规范
tp5中对类,文件名,函数和方法的命名规范如下: 类名和类文件名保持一致,并统一采用驼峰法命名(首字母大写) 类的命名采用驼峰法,并且首字母大写,例如 User.UserType,不需要添加contr ...
- tp5 为什么使用单例模式
首先我们要知道明确单例模式这个概念,那么什么是单例模式呢?单例模式顾名思义,就是只有一个实例.作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例, 这个类我们 ...
- 玄机-第二章日志分析-mysql应急响应
目录 前言 简介 应急开始 准备工作 日志分析 步骤 1 步骤 2 步骤 3 步骤 4 总结 补充mysql中的/var/log/mysql/erro.log 记录上传文件信息的原因 前言 这里应急需 ...
- java中一些空判断|ObjectUtils
为什么用ObjectUtils? 在java中判断对象是否为null,常常不止判断对象是否为null,如果对象是集合,数组,字符串等等特殊类型,还需要检查是否为空(元素个数为0或者长度为0)Objec ...
- 备份服务器eBackup
目录 软件包方式安装eBackup备份软件 1.前景提要 2.创建虚拟机 3.安装备份软件. 4.安装 eBackup 补丁 5.配置 eBackup 服务器 6.访问web界 ...
- 【Java / JavaScript】AES加密解密
Java封装的AES加密解密工具类: 几个重要的参数信息 - 需要指定一个密钥串sKey 密钥内容自定义 数字 + 字母 + 特殊符号 - 加密方式为 AES - AES下面的模式ECB - ECB下 ...
- 【REGX】正则表达式 选中空白行
参考地址: https://www.cnblogs.com/peijyStudy/p/13201576.html VScode并列替换不够智能,我需要等行粘贴,结果SHIFT+ALT复制内容粘贴上去就 ...
- 中国特供阉割版4090D建议安装最新驱动,据说不然的话会报error:4090和4090D对比
资料来源: https://www.bilibili.com/video/BV1oa4y127fG/?spm_id_from=333.999.0.0&vd_source=f1d0f27367a ...