元件 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>
<button @click="update">Increment</button>
</template>
父元件可以使用 v-model
繫結一個值
template
<!-- Parent.vue -->
<Child v-model="countModel" />
defineModel()
返回的值是一個 ref。它可以像任何其他 ref 一樣被訪問和修改,不同之處在於它充當父值和區域性值之間的雙向繫結
- 它的
.value
與父元件繫結的v-model
值同步; - 當它被子元件修改時,也會更新父元件繫結的值。
這意味著您還可以使用 v-model
將此 ref 繫結到原生輸入元素,使包裝原生輸入元素同時提供相同的 v-model
使用方式變得簡單
vue
<script setup>
const model = defineModel()
</script>
<template>
<input v-model="model" />
</template>
內部原理
defineModel
是一個便利宏。編譯器將其擴充套件為以下內容
- 一個名為
modelValue
的 prop,該 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="foo"
將被編譯為
template
<!-- Parent.vue -->
<Child
:modelValue="foo"
@update:modelValue="$event => (foo = $event)"
/>
如你所見,這要冗長得多。然而,瞭解底層發生的事情是有幫助的。
因為 defineModel
聲明瞭一個 prop,所以你可以透過將其傳遞給 defineModel
來宣告底層 prop 的選項
js
// making the v-model required
const model = defineModel({ required: true })
// providing a default value
const model = defineModel({ default: 0 })
警告
如果您為 defineModel
prop 提供了預設值,並且父元件沒有為該 prop 提供任何值,這可能會導致父元件和子元件之間的解同步。在下面的示例中,父元件的 myRef
是未定義的,但子元件的 model
是 1
js
// child component:
const model = defineModel({ default: 1 })
// parent component:
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 選項,它們應放在模型名稱之後傳遞
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
都將同步到不同的屬性,無需在元件中新增額外選項。
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" />
新增到元件 v-model
的修飾符可以透過解構 defineModel()
返回值來訪問,如下所示
vue
<script setup>
const [model, modifiers] = defineModel()
console.log(modifiers) // { capitalize: true }
</script>
<template>
<input type="text" v-model="model" />
</template>
為了根據修飾符有條件地調整讀取/寫入值的方式,我們可以將 get
和 set
選項傳遞給 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="props.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>