牛逼!Vue3.5的useTemplateRef讓ref操作DOM更加絲滑

来源:https://www.cnblogs.com/heavenYJJ/p/18395547
-Advertisement-
Play Games

前言 vue3中想要訪問DOM和子組件可以使用ref進行模版引用,但是這個ref有一些讓人迷惑的地方。比如定義的ref變數到底是一個響應式數據還是DOM元素?還有template中ref屬性的值明明是一個字元串,比如ref="inputEl",怎麼就和script中同名的inputEl變數綁到一塊了 ...


前言

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。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • title: 使用 nuxi prepare 命令準備 Nuxt 項目 date: 2024/9/7 updated: 2024/9/7 author: cmdragon excerpt: 摘要:本文介紹nuxi prepare命令在Nuxt.js項目中的使用,該命令用於創建.nuxt目錄並生成類型 ...
  • 受 LabelImg 啟發的基於 web 的圖像標註工具,基於 Vue 框架 喲,網友們好,年更鴿子終於想起了他的博客園密碼。如標題所述,今天給大家帶來的是一個基於 vue2 的圖像標註工具。至於它誕生的契機呢,應該是我導 pass 掉了我的提議(讓甲方使用 LabelImg 進行數據標註),說是要 ...
  • title: 使用 nuxi init 創建全新 Nuxt 項目 date: 2024/9/6 updated: 2024/9/6 author: cmdragon excerpt: 摘要:本文介紹瞭如何使用nuxi init命令創建全新的Nuxt.js項目,包括安裝所需環境、命令使用方法、指定模板 ...
  • element-ui 的組件庫中沒有圖片下拉選擇組件,基於 el-select 組件做的改動並不能完全滿足需求,因此決定重寫一個。 從頭到尾做下來收穫很多,我決定把實現過程中遇到的問題記錄一下。 效果圖 線上試用地址 設計要點 接下來將上面代碼中的關鍵部分拆分介紹 1. 回顯選中的圖片和 label ...
  • Vue3.5正式版在這兩天發佈了,網上已經有了不少關於Vue3.5版本的解讀文章。但是歐陽發現這些文章對3.5中新增的功能介紹都不是很全,所以導致不少同學有個錯覺,覺得Vue3.5版本不過如此 ...
  • title: 使用 nuxi info 查看 Nuxt 項目詳細信息 date: 2024/9/5 updated: 2024/9/5 author: cmdragon excerpt: 摘要:文章介紹了nuxi info命令的使用方法,這是一個Nuxt.js命令行工具,用於查看當前或指定Nuxt項 ...
  • 第一步:安裝 npm 使用以下命令安裝npm install vue-grid-layout --save yarn 使用以下命令安裝yarn add vue-grid-layout 第二步:配置全局變數 import { createApp } from 'vue' import App from ...
  • 前言 前端導出表格有很多種方案,但是表格樣式一旦複雜了,那麼就得用代碼寫excel的樣式,還是比較麻煩的。每次樣式不一樣,就得重新寫,這時使用表格模板的優勢就體現出來了,想導出不同樣式的表格直接修改表格模板即可。 方案 我找了兩種方案: 1、使用xlsx-template,利用模板語法在xlsx中占 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...