工作筆記︱Vue3 Template Refs 父子元件間取得DOM元素的技巧

Ashley Chou
8 min read6 days ago

--

由 ‍Birger Eriksson — 自己的作品, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=18034500

好久沒發文了,這陣子有點太怠惰~/ᐠ .ᆺ. ᐟ\ノ

實際上是今年年初到4月底時正值考慮是否換工作的時期,因為前公司的一些原因,導致工程師們全員出走R,本來沒有要這麼快換工作的我也算是被迫走人。於是今年4月初到4月中都在投履歷找工作,4月底離職前就拿到Offer順利能在5月中接軌上班。於是8月中剛結束目前公司的工作試用期,雖然目前公司的工作對比前公司來說難度降低了,但畢竟以轉職前端一年的菜鳥來說能學的東西還有很多,剛好這陣子工作進度比較不緊湊,可以讓我將這段時間學到的新東西做個紀錄分享。

我們在前端開發上,應該蠻大的機率會需要去選取和操控 HTML 元素進而做些事情,在過往使用原生JavaScript時,我們通常會用以下方式直接操作DOM:

// 通過HTML元素的CSS Selector來選取該元素
const element = document.querySelector('.test') // 引號內的名稱是元素的class name

// 通過HTML元素的id屬性來選取該元素
const element = document.getElementById(test) // 括弧內直接放元素的id名稱

但在Vue中我們需要訪問底層的DOM元素時,我們還可以使用特殊的 ref attribute,在官方文件中有提到:ref 是一個特殊的 attribute,和 v-for 章節中提到的 key 類似。它允許我們在一個特定的 DOM 元素或子組件實例被掛載後,獲得對它的直接引用。

通常它的用法如下:

  1. 會先宣告一個ref attribute 值的 ref,初始值會設為null
  2. 接著在<template>中綁定在需要取得DOM的html元素上
// JavaScript寫法:
const chatRoomContainer = ref(null);

// typeScript寫法:
const chatRoomContainer = ref<HTMLElement | null>(null);
<template>  
<section class="chatroom" ref="chatRoomContainer">
</section>
</template>

如此一來當我們想要對此元素做操作時,以上述例子來說可以直接拿chatRoomContainer.value(請記得加上.value)去做後續操作,它會等於是以下的chatRoomContainer:

const chatRoomContainer = document.querySelector('.chatroom')

上述是Template Refs的基本用法,相信並不難懂!但我在開發過程中卻發現~因為開發的畫面越來越複雜,導致<template>中的html會越寫越多行,於是我們一定會將某些html拆出去獨立成一個元件,接著會在頁面中引入使用。

如此一來,我們要如何在父元件中以refs去取得子元件中的DOM元素,或是在子元件以refs取得父元件中的DOM元素呢?

當然如果使用JavaScript直接操作DOM的話就沒有這個問題,只要確定取得的class Name和ID是頁面上的唯一值,就沒有跨越父子不同元件的問題。

前不久剛好在工作中遇到的需要在不同元件中,以Refs選取上層或下層元件的DOM 元素,於是將自己的發現新大陸記錄下來~以下會使用Vue3 + TypeScript來說明ξ( ✿>◡❛)

在父元件中取得子元件的Template Refs

  1. 父元件中:
<template>
<ChildComponent ref="childRef"/>
<button @click="handleClick">Access Child Ref</button>
</template>
<script setup lang="ts">
import { ref } from "vue";
import ChildComponent from "@/components/forTest/test.vue";

const childRef = ref<InstanceType<typeof ChildComponent> | null>(null);

const handleClick = () => {
if (childRef.value) {
const childElement = childRef.value.$refs.child as HTMLElement;
if (childElement) {
childElement.style.color = 'red' // 改變字體顏色
}
}
};
</script>

↑ 上面const childElement = childRef.value.$refs.child as HTMLElement中的child,就是下面子元件中所綁定的ref

2. 子元件中:

<template>
<div class="child" >
<p class="text-white" ref="child">123</p> // 這邊的child
</div>
</template>

上述範例進行實作後就會變成以下的效果:

在子元件中取得父元件的Template Refs

在子元件中取得父元件的Template Refs則需要使用到Vue的另一個知識點provide/inject(依賴注入),文章底部會放在Vue官方文檔的說明。

  1. 父元件中:
<template> 
<section
class="chatroom w-[100%] flex flex-col items-center p-8 gap-4"
ref="chatRoomContainer"
>
<div class="container w-[100%] h-[100%] flex gap-6">
<PeopleColumn />
<div class="mid-block w-[91.4%] flex flex-col gap-6 items-center">
<ChatroomHeader />
<ChatroomDialogue />
</section>
</template>
<script setup lang="ts">
import { provide } from 'vue'
const chatRoomContainer = ref<HTMLElement | null>(null);

// 提供 ref 值給子元件
provide("chatRoomContainer", chatRoomContainer); // 使用provide
</script>

2. 子元件中:

<script setup lang="ts">
import { inject } from 'vue'

// 注入父元件提供的 chatRoomContainer
const chatRoomContainer = inject(
"chatRoomContainer"
) as Ref<HTMLElement | null>;

const finishSetting = () => {
if (chatRoomContainer.value) {
console.log(chatRoomContainer.value);
chatRoomContainer.value.style.backgroundImage = `url("src/assets/images/chatroom_bg${chooseId.value}.jpg")`;
chatRoomContainer.value.style.backgroundSize = "cover";
chatRoomContainer.value.style.backgroundRepeat = "no-repeat";
}
};
</script>

↑ 如此一來就可以在子元件中抓到父元件的ref,並且對此進行style方面的修改。

身為一個剛轉職前端滿一年的菜鳥,每每在工作上碰到新的知識點或是自己未曾遇到的問題,其實都覺得很慶幸(?)因為很開心又能學到新東西,還有拓展自己的技能樹,最近在目前的公司總是能碰到或解決一些過去未曾面對到的問題(感謝前公司的前輩們讓我這個後人乘涼👀)。這也告訴我們沒事多看看官方文檔,既然要用這個工具,就要先看懂說明文件怎麼用它!(我這個因工作所需才在工作中邊學寫Vue,半路出家的傢伙!😵)

--

--

Ashley Chou

2023/01從Alpha Camp結業,休息5個月後開始轉職找工作,順利於2023/08以「前端工程師」的身份重返職場。前端框架一開始以學習React起家,但進入工作後主要使用Vue ,亦有寫過Nuxt3的經驗,工作之餘會督促自己撰寫文章記錄所學,前端之路且走且學永無止盡呀!