iT邦幫忙

2024 iThome 鐵人賽

DAY 16
2
Modern Web

Vue 3 初學者:用實作帶你看過核心概念系列 第 16

Vue 3 用實作帶你看過核心概念 - Day 16:使用 ref 屬性引用模板元素

  • 分享至 

  • xImage
  •  

文章背景圖

目錄

  • ref 屬性搭配第三方 Boostrap Modal 使用
  • ref 屬性搭配 Video Web API 使用
  • v-for 搭配陣列儲存 ref 屬性使用
  • 動態 ref 屬性綁定方法
  • 組件實例搭配 ref 使用
  • 總結

ref 屬性搭配第三方 Boostrap Modal 使用

雖然 Vue 透過模板幫助我們處理了大部分的DOM 操作,但在某些情況下,仍然需要手動操作 DOM。例如,在使用第三方套件(如 Bootstrap 的 Modal)時,或者需要調用像video.play()video.pause()這類的原生 Web API 時,我們必須直接與 DOM 元素進行交互。此時,可以使用ref屬性為DOM 元素設置標記,以便方便地引用並手動操作這些元素。

觀察BootstrapModal 組件可以發現,若要使用 JavaScript 來控制 Modal,需要先創建一個 Bootstrap Modal 的實例,並將對應的 Modal 元素 ID 傳遞給這個實例。完成這一步後,就可以透過 modal.show()modal.hide()這樣的方法來控制 Modal 的開啟和關閉。

原生的 Boostrap Modal 使用流程說明:

  1. 初始化透過Boostrap實例綁定對應顯示的 Modal DOM 元素。
  2. 監聽 Modal 內的儲存(Save)按鈕跟關閉(Close)按鈕,點擊的時候觸發對應的方法show()hide()方法。

👉 Boostrap Modal 純 javaScript 寫法實作連結

HTML:

  <div class="modal" tabindex="-1" role="dialog" id="modal">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title">Modal title</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <p>Modal body text goes here.</p>
        </div>
        <div class="modal-footer">
          <button id="saveButton" type="button" class="btn btn-primary">Save changes</button>
          <button id="closeButton" type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
        </div>
      </div>
    </div>
  </div>

javascript:

const myModal = new bootstrap.Modal(document.getElementById("modal"));

// 開啟 Modal 按鈕
document.querySelector("#openModal").addEventListener("click", function () {
  myModal.show();
});

// Modal 儲存按鈕
document.querySelector("#saveButton").addEventListener("click", function () {
  myModal.hide();
});

// Modal 關閉按鈕
document.querySelector("#closeButton").addEventListener("click", function () {
  myModal.hide();
});

接著我們將上面的控制方式改成使用 Vue 進行管控。

👉 Vue3 Options API Bootstrap Modal ref 實作連結

  1. 原先透過document.getElementById("modal")的部分取得的 DOM 元素,改成透過ref的方式取得。
  2. 監聽 Modal 內的儲存(Save)按鈕跟關閉(Close)按鈕,透過v-on綁定click事件,點擊的時候觸發對應的方法show()hide()方法。

Vue Template:

<div id="app">
  <div class="p-4">
    <button @click="openModal" id="openModal" type="button" class="btn btn-primary">打開 modal</button>
    <div class="modal" tabindex="-1" role="dialog" ref="modal">
      <div class="modal-dialog" role="document">
        <div class="modal-content">
          <div class="modal-header">
            <h5 class="modal-title">Modal title</h5>
            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
          </div>
          <div class="modal-body">
            <p>Modal body text goes here.</p>
          </div>
          <div class="modal-footer">
            <button @click="closeModal" id="saveButton" type="button" class="btn btn-primary">Save changes</button>
            <button @click="closeModal" id="closeButton" type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

javascript:

const rootComponent = {
  data() {
    return {
      myModal: null
    };
  },
  methods: {
    // 開啟 Modal
    openModal() {
      this.myModal.show();
    },
    // 關閉 Modal
    closeModal() {
      this.myModal.hide();
    }
  },
  mounted() {
    this.myModal = new bootstrap.Modal(this.$refs.modal);
  }
};

⭐ 預設ref對象會是undefined如果需要讀取到其對應的DOM 元素需要在mouted模板渲染完成之後的階段。

ref 搭配 Video Web API 使用

除了上述與第三方套件搭配的案例外,在使用像video這類的原生 HTML 標籤時,也經常需要使用其內建的 Web API,例如:play()pause()方法來控制播放行為。在這些情況下,可以利用 Vue 的ref屬性來標記這些 DOM 元素,以便進行手動控制和操作。

👉 Vue3 Options API HTML video tag with ref 實作連結

Vue Template:

<video src="https://www.w3schools.com/html/mov_bbb.mp4" ref='video'></video>
<div class="btn-group" role="group" aria-label="Basic example">
  <button @click="play" type="button" class="btn btn-secondary">play</button>
  <button @click="pause" type="button" class="btn btn-secondary">pause</button>
  <button @click="reset" type="button" class="btn btn-secondary">reset</button>

javascript:

const rootComponent = {
  data() {
    return {
      videoInstance: null
    };
  },
  methods: {
    play() {
      this.videoInstance.play();
    },
    pause() {
      this.videoInstance.pause();
    },
    reset() {
      this.videoInstance.pause();
      this.videoInstance.currentTime = 0;
    }
  },
  mounted() {
    this.videoInstance = this.$refs.video;
  }
};

v-for 搭配陣列儲存 ref 屬性使用

在 Vue 中,當refv-for 指令一起使用時,Vue 會自動將生成的 DOM 元素存儲在一個陣列中。可以透過this.$refs訪問這些元素。

👉 Vue3 Options API v-for 搭配 ref 陣列實作連結

Vue Template:

<ul>
  <li v-for="item in productList" :key="item" ref="items">{{ item }}</li>
</ul>

javascript:

const app = createApp({
  data() {
    return {
      productList: ["Apple", "Orange", "Banana"]
    };
  }
  mounted() {
    console.log("this.items", this.$refs.items);
  }
});

⭐ 無法保證綁定陣列中ref 屬性的順序與原始陣列數據的順序一致

動態 ref 屬性綁定方法

可以透過綁定一個動態 ref 屬性,在每次模板更新時將該ref傳入到一個函數中進行處理。如果綁定的元素被銷毀,傳入函數的參數值將會自動變為null

流程說明:

  1. 透過按【切換顯示使用者帳號輸入框】按鈕切換使用者帳號輸入欄位的顯示與隱藏。
  2. 當使用者帳號輸入欄位出現時,透過動態綁定的 ref 屬性,自動將焦點聚焦到該輸入框,達到 focus() 效果。

👉 Vue3 Options API ref 動態綁定方法(method)實作連結

動態綁定 ref 屬性取得元素焦點

Vue Template:

<div v-if="isShowAccount" class="input-group">
  <label for="inputAccount">使用者帳號:</label>
  <input id="inputAccount" type="text" :ref="setRef">
</div>
<button @click="register" type="button">切換顯示使用者帳號輸入框</button>

javascript:

  methods: {
    setRef(ref) {
      // ref 是動態傳入的屬性
      if (ref) {
        this.$nextTick(() => {
          ref.focus();
        });
      }
    },
    register() {
      this.isShowAccount = !this.isShowAccount;
    }
  }
...略

組件實例搭配 ref 使用

ref 屬性被應用在 HTML 元素上時,它會標記並引用實際的HTML DOM 元素;而當ref 屬性被應用在子組件上時,ref 屬性則會引用該子組件的實例。透過這個ref 屬性,可以直接訪問子組件的內部數據和方法。不過,過度使用ref 屬性來操作子組件的資料和行為,可能會導致資料流混亂,需要謹慎使用。

流程說明:

  1. 子組件定義了一個計數器,使用變數 childCount 來記錄當前的計數值,並提供 addNum 方法來增加 childCount 的值,每次增加 1。
  2. 在根組件中,透過 ref 屬性標記子組件的實例,並定義 getChildCount 方法來獲取子組件當前的 childCount 值,以及 addNum 方法來觸發子組件內部的 addNum 方法。

父組件透過 ref 屬性讀取子組件屬性及使用其方法

👉 Vue3 Options API ref 取得組件實體內數據及方法實作連結

父組件 Vue Template:

<child-component ref="child"></child-component>
<p>取得子組件的響應式變數值:{{ RootCount }}</p>
<button @click="getChildCount" type="button">取得子組件的響應試變數值</button>
<button @click="addNum" type="button">使用子組件 addNum 增加子組件 childCount 變數值</button>

⭐ 【 取得子組件的響應試變數值 】按鈕可以取得子組件childCount當前變數值。
⭐ 【 使用子組件 addNum 讓子組件 childCount 變數值 + 1 】按鈕可以取得子組件addNum 函數,並且將子組件 childCount 變數值 + 1。

父組件 javascript:

const rootComponent = {
  data() {
    return {
      RootCount: 0
    };
  },
  methods: {
    // 取得子組件數據 childCount
    getChildCount() {
      this.RootCount = this.$refs.child.childCount;
    },
    // 使用子組件內的 addNum 方法
    addNum() {
      this.$refs.child.addNum();
    }
  },
  components: {
    childComponent
  }
};

子組件 javascript:

const childComponent = {
  template: "#childComponent",
  data() {
    return {
      // 紀錄計數器當前數字
      childCount: 0
    };
  },
  methods: {
    // 將變數 childCount + 1
    addNum() {
      this.childCount += 1;
    }
  }
};

如果希望限制子組件實例中哪些數據和方法可以被外部訪問,可以在子組件中使用expose 選項,來控制外層透過ref 屬性能夠讀取的範圍。

以上面的案例為例,可以使用expose選項來限制只有addNum方法能被外部通過 ref 屬性訪問,而子組件的數據childCount則無法被外部讀取。

當呼叫根組件getChildCount方法試圖取得子組件childCount參數的時候,會回傳undefined

👉 Vue3 Options API ref 取得組件實體內數據及方法(搭配 expose 限制)實作連結

javascript:

const childComponent = {
  expose: ["addNum"],
  template: "#childComponent",
  data() {
    return {
      // 紀錄計數器當前數字
      childCount: 0
    };
  },
  methods: {
    // 將變數 childCount + 1
    addNum() {
      this.childCount += 1;
    }
  }
};

結論

  • 第三方套件與 Web API 的應用:當需要對HTML 元素進行操作時,可以使用 Vue 的ref 屬性來標記對應的HTML DOM 元素,方便與第三方套件或 Web API 結合使用。
  • v-for 搭配 ref 屬性:在使用v-for生成多個元素時,可以使用ref 屬性來將這些元素存儲為一個陣列,便於後續進行批次操作。
  • 動態 ref 屬性綁定方法:可以透過數的方式動態綁定 ref,在需要時對原生 HTML 元素進行操作。當綁定的元素或組件被銷毀時,函數中的ref 參數會自動變為null
  • 父組件透過 ref 屬性讀取子組件的數據及方法
    • 在父組件中,可以通過ref 綁定子組件實例,直接訪問子組件內部的變數或調用其方法。需要注意的是,這種操作可能會導致資料流的混亂,因此應謹慎使用。
    • 如果子組件需要限制父組件通過ref 屬性訪問內部數據和方法的範圍,可以使用expose 參數來定義哪些數據和方法可以被外部訪問。

上一篇
Vue 3 用實作帶你看過核心概念 - Day 15:監聽器(Watchers)
下一篇
Vue 3 用實作帶你看過核心概念 - Day 17:組件基礎 - 全局註冊與區域註冊
系列文
Vue 3 初學者:用實作帶你看過核心概念30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言