在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
來簡化原本的屬性
和事件綁定
,即:modal-value="modelValue"
和 @update:modal-value="setModalValue"
的使用方式進行說明。
在父組件中,我們將modelValue
值傳遞給子組件,並使用setModalValue
事件來接收子組件更新的數據。屬性或事件綁定的原則是:前面的名稱對應子組件
使用的數據
或自定義事件
名稱,而後面的名稱則對應父組件中相應的數據
或方法名稱
(口訣:前內後外)。
需要特別注意的是,事件綁定的命名方式在這裡是比較特殊的,格式為@update:傳入的變數名稱
。這是Vue
使用 v-model
在組件中進行雙向綁定時的命名規則之一。當子組件中綁定的變數發生變化時,Vue
會自動觸發對應的事件,從而更新父組件中的相關數據,實現雙向綁定。
透過以下的輸入框範例來讓大家更加理解。
流程說明:
modalValue
將值透過prop
的方式動態傳遞給子組件,提供子組件的輸入框作為初始值使用。input
事件時,會發送自定義的update:modal-value
事件,並將當前的輸入值作為參數傳遞給父組件。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>
⭐ 這個案例中有一個非常重要的概念:數據的管理者和數據的呈現者由誰負責。父組件應負責數據的管理,子組件則負責數據的顯示和操作。當我們需要修改數據時,應由父組件統一進行變更,這樣能確保所有使用該數據的子組件保持同步更新。
在上面的案例中,我們可以看到,當子組件接收到父組件傳入的值時,仍然需要透過input
事件將子組件中的輸入值透過$emit
發送給父組件。這是因為子組件不應該直接修改props
中的值,而是應該透過事件告知父組件進行更新。這部分我們可以使用計算屬性
來簡化這個過程,讓屬性綁定和事件發送之間的關係更加清楚。
首先,我們需要理解整個值的流向:子組件輸入框的預設值應來自父組件的props
,當使用者在輸入框中修改值時,我們需要觸發一個事件,通知父組件去修改該值,而不是直接在子組件中變更props
。這樣的行為可以通過計算屬性的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);
}
}
}
};
如果子組件中沒有使用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
的方式進行數據綁定。以下透過一個產品列表包產品資訊物件的案例說明。
流程說明:
v-for
指令迭代出每個陣列選項product
product
物件內的兩個屬性productName
及price
透過v-model
綁定子組件的輸入框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
本身提供了一些內置的修飾符(例如:.trim
、.number
、.lazy
),這些修飾符可以幫助我們在數據綁定時處理常見的需求。如果你希望自定義v-model
的修飾符,Vue
也提供了一種方法,可以透過v-model
名稱加上Modifiers
的方式來實現。
當v-model
需要寫入數據時,你可以利用自定義修飾符來判斷v-model
是否應用了某個修飾符,然後根據該修飾符的存在來進行相應的處理,這樣可以更靈活地控制數據的更新邏輯。
這邊透過自定義修飾符antonio
將子物件輸入框的值轉化成全部大寫的案例進行說明:
👉 Vue3 Options API 組件 v-model 值自定義修飾符實作連結
流程說明:
Template
中,透過v-model:text-value.antonio.trim
傳遞自定義修飾符到子組件set
函數中,會判斷是否存在自定義的修飾符父組件 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 函數
根據修飾符的存在來判斷是否需要執行額外處理,例如將值轉換成大寫。
:modelValue
綁定父組件的props
,並通過 @update:modelValue
來接收子組件的回傳值,現在可以直接使用v-model
,大幅簡化了數據綁定與事件處理的過程。計算屬性
的get 函數
來讀取父組件傳入的props 初始值
,並透過set
函數在數據變更時觸發事件,將更新的數據傳遞回父組件。這樣由父組件負責數據管理,子組件則專注於界面的呈現,可以更清楚劃分父子組件的職責。v-model
,且不使用預設的modelValue
名稱時,需要顯式地綁定v-model
傳遞的props
以進行多參數的數據傳遞。set
方法,根據不同修飾符實現特定的邏輯操作。