首页 > 其他分享 >牛逼!Vue3.5的useTemplateRef让ref操作DOM更加丝滑

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

时间:2024-09-04 14:15:17浏览次数:11  
标签:丝滑 变量 DOM ref refs value inputEl useTemplateRef

前言

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。

标签:丝滑,变量,DOM,ref,refs,value,inputEl,useTemplateRef
From: https://www.cnblogs.com/heavenYJJ/p/18395547

相关文章

  • JS的DOM高级编程和动态添加表格行的小案例实现
    DOM高级编程(DocumentobjectModal)DOM概述DOM-DocumentObjectModal,它是W3C国际组织的一套Web标准DOM是一种与浏览器、平台、语言无关的接口Dom认为:html文档中每个成员都是一个节点,根据节点的不同,可分为:文档节点(document)元素节点(element)属性节点(attribute)文本节点(tex......
  • 丝滑解决Chatgpt频繁奔溃、断网掉线问题
    事件缘由这段时间使用Chatgpt的时候频繁出现somethingwrong等断网掉线问题,中间还频繁出现物品转向的人机验证(我那么具有迷惑性吗...),被烦的不行。后面了解到有一个KeepChatgpt的插件可以解决这些问题,于是把这个方法介绍给大家,希望大家也都可以丝滑gpt。【现在看到这个图就害怕.......
  • 如何高效操作DOM ?
    1.为什么说DOM操作耗时1.1线程切换浏览器为了避免两个引擎同时修改页面而造成渲染结果不一致的情况,增加了另外一个机制,这两个引擎具有互斥性,也就是说在某个时刻只有一个引擎在运行,另一个引擎会被阻塞。操作系统在进行线程切换的时候需要保存上一个线程执行时的状态信息并......
  • 如何通过 DOM 而不是通过表来呈现数据
    通过DOM(文档对象模型)呈现数据而不是通过表格,可以使用HTML的其他元素来组织和展示数据。以下是一些常见的方法:使用列表(<ul>或<ol>):将数据项作为列表项呈现,可以使用无序列表<ul>或有序列表<ol>。每个数据项可以是列表项<li>元素。使用段落(<p>):将每个数据项作为一个段落呈现,可......
  • 基于Python的机器学习系列(14):随机森林(Random Forests)
    简介        在上一节中,我们探讨了Bagging方法,并了解到通过构建多个树模型来减少方差是有效的。然而,Bagging方法中树与树之间仍然可能存在一定的相关性,降低了方差减少的效果。为了解决这个问题,我们引入了随机森林(RandomForests),这是一种基于Bagging的增强技术,通过在每......
  • DynamiCrafter:Animating open-domain images with video diffusion priors
    1.Method图像条件视频生成,1.1ImageDynamicsfromVideoDiffusionPriors1.1.1文本对齐的上下文表征文本嵌入通过clip构建,图像通过clip编码,主要代表语义层面的视觉内容,未能捕获图像的完整信息,为了提取更完整的信息,使用来自clip图像vit最后一层的全视觉标记,该token在条......
  • vue使用html2canvas将页面dom生成图片,解决页面中带有图片导出
    安装npm installhtml2canvas引入importhtml2canvas from 'html2canvas'使用this.$refs.canvasToPic是div的dom,只要在这个div中的区域都可以生成图片1this.$nextTick(()=>{2html2canvas(this.$refs.canvasToPic,{useCORS:true,logging:true}).......
  • WPF mouse down on canvas and draw shapes which render with random colors
    //customcontrol//xaml<UserControlx:Class="WpfApp307.ElpTbk"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"......
  • C#学习笔记- 随机函数Random()的用法详解
    原文链接:https://www.jb51.net/article/90933.htmRandom.Next()返回非负随机数;Random.Next(Int)返回一个小于所指定最大值的非负随机数Random.Next(Int,Int)返回一个指定范围内的随机数,例如(-100,0)返回负数1、random(number)函数介绍random(number)返回一个0~number-1之间......
  • IntersectionObserver实现监听dom进入浏览器视口
    letimg=document.getElementsByTagName("img");constobserver=newIntersectionObserver(entries=>{//entries是被观察的元素集合for(leti=0,len=entries.length;i<len;i++){letentries=entries[i];//通过这个属性判断是否在视口......