組件 v-model
基本用法
v-model
可以在組件上使用以實現雙向綁定。
從 Vue 3.4 開始,推薦的實現方式是使用 defineModel()
宏:
vue
<!-- Child.vue -->
<script setup>
const model = defineModel()
function update() {
model.value++
}
</script>
<template>
<div>Parent bound v-model is: {{ model }}</div>
</template>
父組件可以用 v-model
綁定一個值:
template
<!-- Parent.vue -->
<Child v-model="countModel" />
defineModel()
返回的值是一個 ref。它可以像其他 ref 一樣被訪問以及修改,不過它能起到在父組件和當前變量之間的雙向綁定的作用:
- 它的
.value
和父組件的v-model
的值同步; - 當它被子組件變更了,會觸發父組件綁定的值一起更新。
這意味著你也可以用 v-model
把這個 ref 綁定到一個原生 input 元素上,在提供相同的 v-model
用法的同時輕鬆包裝原生 input 元素:
vue
<script setup>
const model = defineModel()
</script>
<template>
<input v-model="model" />
</template>
底層機制
defineModel
是一個便利宏。編譯器將其展開為以下內容:
- 一個名為
modelValue
的 prop,本地 ref 的值與其同步; - 一個名為
update:modelValue
的事件,當本地 ref 的值發生變更時觸發。
在 3.4 版本之前,你一般會按照如下的方式來實現上述相同的子組件:
vue
<!-- Child.vue -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="props.modelValue"
@input="emit('update:modelValue', $event.target.value)"
/>
</template>
然後,父組件中的 v-model="modelValue"
將被編譯為:
template
<!-- Parent.vue -->
<Child
:modelValue="foo"
@update:modelValue="$event => (foo = $event)"
/>
如你所見,這顯得冗長得多。然而,這樣寫有助於理解其底層機制。
因為 defineModel
聲明了一個 prop,你可以通過給 defineModel
傳遞選項,來聲明底層 prop 的選項:
js
// 使 v-model 必填
const model = defineModel({ required: true })
// 提供一個默認值
const model = defineModel({ default: 0 })
WARNING
如果為 defineModel
prop 設置了一個 default
值且父組件沒有為該 prop 提供任何值,會導致父組件與子組件之間不同步。在下面的示例中,父組件的 myRef
是 undefined,而子組件的 model
是 1:
js
// 子組件:
const model = defineModel({ default: 1 })
// 父組件
const myRef = ref()
html
<Child v-model="myRef"></Child>
v-model
的參數
組件上的 v-model
也可以接受一個參數:
template
<MyComponent v-model:title="bookTitle" />
在子組件中,我們可以通過將字符串作為第一個參數傳遞給 defineModel()
來支持相應的參數:
vue
<!-- MyComponent.vue -->
<script setup>
const title = defineModel('title')
</script>
<template>
<input type="text" v-model="title" />
</template>
如果需要額外的 prop 選項,應該在 model 名稱之後傳遞:
js
const title = defineModel('title', { required: true })
3.4 之前的用法
vue
<!-- MyComponent.vue -->
<script setup>
defineProps({
title: {
required: true
}
})
defineEmits(['update:title'])
</script>
<template>
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>
多個 v-model
綁定
利用剛才在 v-model
參數小節中學到的指定參數與事件名的技巧,我們可以在單個組件實例上創建多個 v-model
雙向綁定。
組件上的每一個 v-model
都會同步不同的 prop,而無需額外的選項:
template
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
vue
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>
<template>
<input type="text" v-model="firstName" />
<input type="text" v-model="lastName" />
</template>
3.4 之前的用法
vue
<script setup>
defineProps({
firstName: String,
lastName: String
})
defineEmits(['update:firstName', 'update:lastName'])
</script>
<template>
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</template>
處理 v-model
修飾符
在學習輸入綁定時,我們知道了 v-model
有一些內置的修飾符,例如 .trim
,.number
和 .lazy
。在某些場景下,你可能想要一個自定義組件的 v-model
支持自定義的修飾符。
我們來創建一個自定義的修飾符 capitalize
,它會自動將 v-model
綁定輸入的字符串值第一個字母轉為大寫:
template
<MyComponent v-model.capitalize="myText" />
通過像這樣解構 defineModel()
的返回值,可以在子組件中訪問添加到組件 v-model
的修飾符:
vue
<script setup>
const [model, modifiers] = defineModel()
console.log(modifiers) // { capitalize: true }
</script>
<template>
<input type="text" v-model="model" />
</template>
為了能夠基於修飾符選擇性地調節值的讀取和寫入方式,我們可以給 defineModel()
傳入 get
和 set
這兩個選項。這兩個選項在從模型引用中讀取或設置值時會接收到當前的值,並且它們都應該返回一個經過處理的新值。下面是一個例子,展示了如何利用 set
選項來應用 capitalize
(首字母大寫) 修飾符:
vue
<script setup>
const [model, modifiers] = defineModel({
set(value) {
if (modifiers.capitalize) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
return value
}
})
</script>
<template>
<input type="text" v-model="model" />
</template>
3.4 之前的用法
vue
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
const emit = defineEmits(['update:modelValue'])
function emitValue(e) {
let value = e.target.value
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>
<template>
<input type="text" :value="modelValue" @input="emitValue" />
</template>
帶參數的 v-model
修飾符
這裡是另一個例子,展示了如何在使用多個不同參數的 v-model
時使用修飾符:
template
<UserName
v-model:first-name.capitalize="first"
v-model:last-name.uppercase="last"
/>
vue
<script setup>
const [firstName, firstNameModifiers] = defineModel('firstName')
const [lastName, lastNameModifiers] = defineModel('lastName')
console.log(firstNameModifiers) // { capitalize: true }
console.log(lastNameModifiers) // { uppercase: true }
</script>
3.4 之前的用法
vue
<script setup>
const props = defineProps({
firstName: String,
lastName: String,
firstNameModifiers: { default: () => ({}) },
lastNameModifiers: { default: () => ({}) }
})
defineEmits(['update:firstName', 'update:lastName'])
console.log(props.firstNameModifiers) // { capitalize: true }
console.log(props.lastNameModifiers) // { uppercase: true }
</script>