iT邦幫忙

2024 iThome 鐵人賽

DAY 20
1
Modern Web

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

Vue 3 用實作帶你看過核心概念 - Day 20:組件使用 v-model 雙向綁定

  • 分享至 

  • xImage
  •  

文章背景圖

目錄

  • 組件 v-model 前原理說明
  • 父組件透過 v-model 與子組件雙向綁定數據
  • 子組件透過計算屬性實現 v-model 雙向綁定
  • 組件 v-model 多參數傳值使用
  • 組件 v-model 自定義修飾符判斷處理
  • 總結

組件 v-model 前原理說明

Day 13中,我們介紹了表單元素(例如輸入框)如何處理數據雙向綁定。當需要將表單元素與一個響應式變數綁定時,v-model提供雙向綁定功能。這代表當畫面上表單元素的變動會即時影響Vue數據,同時當Vue數據的值改變時,畫面也會立即更新。透過這種雙向綁定,我們可以確保使用者輸入和應用內部狀態之間的同步。

透過以下簡單的範例,我們來複習v-model的基本使用:

當使用者在輸入框中輸入內容時,Vue會自動更新資料中的inputValue。反之,當我們在程式中修改 inputValue的值,對應的輸入框預設值也會立即顯示出來,實現雙向綁定的效果。

👉 Vue3 Options API v-model 表單元素實作連結

Vue Template:

<div id="app">
  <h2>顯示 inputValue:{{ inputValue }}</h2>
  <div class="input-group">
    <label for="input">輸入框:</label>
    <input v-model="inputValue" id="input" type="text">
  </div>
</div>

javascript:

const rootComponent = {
  data() {
    return {
      inputValue: ""
    };
  }
};

父組件透過 v-model 與子組件雙向綁定數據

那如果是組件封裝表單元素並進行使用的時候,這部分會如何進行呢?

這裡我們以組件的v-model來簡化原本的屬性事件綁定,即:modal-value="modelValue"@update:modal-value="setModalValue"的使用方式進行說明。

在父組件中,我們將modelValue值傳遞給子組件,並使用setModalValue事件來接收子組件更新的數據。屬性或事件綁定的原則是:前面的名稱對應子組件使用的數據自定義事件名稱,而後面的名稱則對應父組件中相應的數據方法名稱(口訣:前內後外)。

需要特別注意的是,事件綁定的命名方式在這裡是比較特殊的,格式為@update:傳入的變數名稱。這是Vue使用 v-model在組件中進行雙向綁定時的命名規則之一。當子組件中綁定的變數發生變化時,Vue會自動觸發對應的事件,從而更新父組件中的相關數據,實現雙向綁定。

透過以下的輸入框範例來讓大家更加理解。

流程說明:

  1. 父組件透過modalValue將值透過prop的方式動態傳遞給子組件,提供子組件的輸入框作為初始值使用。
  2. 子組件在監聽input事件時,會發送自定義的update:modal-value事件,並將當前的輸入值作為參數傳遞給父組件。
  3. 父組件接收到子組件發送的事件後,會將參數中的當前輸入值寫入modalValue,從而實現數據的同步更新。

👉 Vue3 Options API 組件 v-model 組成原理實作連結

父組件 Vue Template:

  <child-component :model-value="modelValue" @update:model-value="setModalValue"></child-component>

父組件 javascript:

const rootComponent = {
  data() {
    return {
      modelValue: "Test123"
    };
  },
  methods: {
    // 接收從子組件發送出來的事件
    setModalValue(value) {
      this.modelValue = value;
    }
  },
  components: { childComponent }
};

子組件 Vue Template:

<template id="child">
  我是子組件輸入框:<input :value="modelValue" @input="updateModelValue" type="text">
</template>

子組件 javascript:

const childComponent = {
  props: ["modelValue"],
  emits: ["update:modelValue"],
  template: "#child",
  methods: {
    updateModelValue(event) {
      this.$emit("update:modelValue", event.target.value);
    }
  }
};

使用 v-model 簡化數據綁定

當父組件需要控制子組件內部的值時,可以直接使用v-model,這樣可以簡化原本需要分別進行屬性綁定事件觸發的過程。不過,特別注意,子組件內若要修改傳入的值,不能直接寫回props,必須透過@update:自定義事件名稱 來通知父組件更新數據。

父組件 Vue Template:

<!-- 原本的寫法 -->
<child-component1 :model-value="modelValue" @update:model-value="setModalValue"></child-component1>

<!-- 使用 v-model 簡化後的寫法 -->
<child-component1 v-model="modelValue"></child-component1>

⭐ 這個案例中有一個非常重要的概念:數據的管理者和數據的呈現者由誰負責。父組件應負責數據的管理,子組件則負責數據的顯示和操作。當我們需要修改數據時,應由父組件統一進行變更,這樣能確保所有使用該數據的子組件保持同步更新。

子組件透過計算屬性實現 v-model 雙向綁定

在上面的案例中,我們可以看到,當子組件接收到父組件傳入的值時,仍然需要透過input事件將子組件中的輸入值透過$emit發送給父組件。這是因為子組件不應該直接修改props中的值,而是應該透過事件告知父組件進行更新。這部分我們可以使用計算屬性來簡化這個過程,讓屬性綁定和事件發送之間的關係更加清楚。

首先,我們需要理解整個值的流向:子組件輸入框的預設值應來自父組件的props,當使用者在輸入框中修改值時,我們需要觸發一個事件,通知父組件去修改該值,而不是直接在子組件中變更props。這樣的行為可以通過計算屬性的getset函數來處理。

  • get 函數:用來獲取父組件傳入的值,並綁定到輸入框的預設值。
  • set 函數:用來發送事件,將輸入框的變更通知給父組件,請求其更新傳入的值。

延續上面的輸入框案例,改成透過計算屬性改寫。

👉 Vue3 Options API 組件 v-model 子組件使用計算屬性實作連結

子組件 Vue Template:

<template id="child">
  我是 child 輸入框:<input v-model="inputValue" type="text">
</template>

子組件 javascript:

const childComponent = {
  props: ["modelValue"],
  emits: ["update:modelValue"],
  template: "#child",
  computed: {
    inputValue: {
      get() {
        return this.modelValue;
      },
      set(value) {
        this.$emit("update:modelValue", value);
      }
    }
  }
};

組件 v-model 多參數傳值使用

如果子組件中沒有使用modelValue作為預設的參數名稱,而是使用了自定義的名稱,那麼在父組件中使用v-model時,需要顯式地指定綁定的參數名稱,例如:v-model:參數名稱。這樣可以確保多個 v-model值的正確管理,尤其當子組件需要處理多個不同綁定的值時。反之,若子組件預設使用 modelValue,則無需顯式指定參數名稱。

👉 Vue3 Options API 組件 v-model 多參數用法實作連結

父組件 Vue Template:

<child-component v-model:first-name="firstName" v-model:last-name="lastName">

⭐ 請注意,這邊v-model綁定的是props,而v-bind基礎用法綁定的是attribue 屬性,兩者不同。

父組件 Vue Template:

<div id="app">
  <h2>姓名:{{ firstName }} - {{ lastName }}</h2>
  <child-component v-model:first-name="firstName" v-model:last-name="lastName"></child-component>
</div>

子組件 Vue Template:

<template id="child">
  <div class="input-group">
    <lable for="firstName">firstName:</lable>
    <input :value="firstName" @input="updateFirstName" type="text">
  </div>
  <div class="input-group">
    <lable for="firstName">lastName:</lable>
    <input :value="lastName" @input="updateLastName" type="text">
  </div>
</template>

子組件 javascript:

const childComponent = {
  props:['firstName', 'lastName'],
  emits:['update:firstName', 'update:lastName'],
  template: '#child',
  methods:{
    updateFirstName(event){
      this.$emit('update:firstName', event.target.value)
    },
    updateLastName(event){
      this.$emit('update:lastName', event.target.value)
    }
  }
};

當數據類型是包含物件的陣列時,可以通過v-for搭配v-model的方式進行數據綁定。以下透過一個產品列表包產品資訊物件的案例說明。

流程說明:

  1. 父組件透過產品資訊列表使用v-for指令迭代出每個陣列選項product
  2. 父組件將product物件內的兩個屬性productNameprice透過v-model綁定子組件的輸入框
  3. 子組件輸入框透過計算屬性set函數將子組件的變更透過emit通知父組件變更

👉 Vue3 Options API 組件使用 v-for 搭配 v-model 使用實作連結

父組件 Vue Template:

    <child-component v-for="product in productList" v-model:product-name="product.productName" v-model:price="product.price" :key="product.id"></child-component>

子組件 Vue Template:

<template id="child">
  <h2>我是子組件</h2>
  <div class="input-group">
    <label for="productName">productName:</label>
    <input v-model="computedProductName" type="text" id="productName">
  </div>
  <div class="input-group">
    <label for="price">price:</label>
    <input v-model="computedPrice" type="text" id="price">
  </div>
</template>

子組件 javascript:

const childComponent = {
  props: ["productName", "price"],
  emits: ["update:productName", "update:price"],
  template: "#child",
  computed: {
    computedProductName: {
      get() {
        return this.productName;
      },
      set(value) {
        this.$emit("update:productName", value);
      }
    },
    computedPrice: {
      get() {
        return this.price;
      },
      set(value) {
        this.$emit("update:price", value);
      }
    }
  }
};

組件 v-model 自定義修飾符判斷處理

v-model本身提供了一些內置的修飾符(例如:.trim.number.lazy),這些修飾符可以幫助我們在數據綁定時處理常見的需求。如果你希望自定義v-model的修飾符,Vue也提供了一種方法,可以透過v-model名稱加上Modifiers的方式來實現。

v-model需要寫入數據時,你可以利用自定義修飾符來判斷v-model是否應用了某個修飾符,然後根據該修飾符的存在來進行相應的處理,這樣可以更靈活地控制數據的更新邏輯。

這邊透過自定義修飾符antonio將子物件輸入框的值轉化成全部大寫的案例進行說明:

👉 Vue3 Options API 組件 v-model 值自定義修飾符實作連結

流程說明:

  1. 父組件Template中,透過v-model:text-value.antonio.trim傳遞自定義修飾符到子組件
  2. 子組件在計算屬性set函數中,會判斷是否存在自定義的修飾符
  3. 存在的情況會將輸入值均轉換成大寫英文的方式

父組件 Vue Template:

<div id="app">
  <h2>textValue 值:{{ textValue }}</h2>
  <child-component v-model:text-value.Antonio.trim="textValue"></child-component>
</div>

子組件 Vue Template:

<template id="child2">
  我是 child2 輸入框:<input v-model="inputValue" type="text">
</template>

子組件 javascript:

const childComponent = {
  props: {
    textValue: String,
    // 默認是空物件
    textValueModifiers: { default: () => {} }
  },
  emits: ["update:text-value"],
  template: "#child2",
  computed: {
    inputValue: {
      get() {
        console.log("this.textValueModifiers", this.textValueModifiers);
        return this.textValue;
      },
      set(value) {
        if (this.textValueModifiers.antonio) {
          value = value.toUpperCase();
        }
        this.$emit("update:text-value", value);
      }
    }
  }
};

get 函數顯示了v-model綁定的所有修飾符,這些修飾符決定了數據的處理方式。set 函數根據修飾符的存在來判斷是否需要執行額外處理,例如將值轉換成大寫。

透過 console.log 查看 v-model 所有修飾符

總結

  1. 父組件利用 v-model 簡化屬性綁定和事件處理:原本需要使用:modelValue綁定父組件的props,並通過 @update:modelValue來接收子組件的回傳值,現在可以直接使用v-model,大幅簡化了數據綁定與事件處理的過程。
  2. 子組件透過 v-model 綁定計算屬性:子組件可以使用計算屬性get 函數來讀取父組件傳入的props 初始值,並透過set函數在數據變更時觸發事件,將更新的數據傳遞回父組件。這樣由父組件負責數據管理,子組件則專注於界面的呈現,可以更清楚劃分父子組件的職責。
  3. 組件 v-model 多參數傳值使用:當組件使用v-model,且不使用預設的modelValue名稱時,需要顯式地綁定v-model傳遞的props以進行多參數的數據傳遞。
  4. 父組件傳遞自定義修飾符:父組件可以傳遞自定義修飾符,子組件接收後可以根據修飾符進行相應處理。子組件可以結合計算屬性的set方法,根據不同修飾符實現特定的邏輯操作。

上一篇
Vue 3 用實作帶你看過核心概念 - Day 19:子組件向父組件發射事件 - emits
下一篇
Vue 3 用實作帶你看過核心概念 - Day 21: 組件透傳屬性(Attributes)的應用
系列文
Vue 3 初學者:用實作帶你看過核心概念30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言