组件 v-model
基本用法
v-model 可以在组件上使用以实现双向绑定。
从 Vue 3.4 开始,推荐的实现方式是使用 defineModel() 宏:
<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 绑定一个值:
<script setup lang="ts">
import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore()
import modelChild from './components/test/modelChild.vue'
const textColor = computed(() => appStore.getTextColor)
appStore.initTheme()
const countModel = ref(10)
</script>
<template>
<ConfigGlobal>
<p :style="{'color':textColor}" >p标签</p>
{{ countModel }}
<modelChild v-model="countModel"></modelChild>
</ConfigGlobal>
</template>
显示如下:
defineModel() 返回的值是一个 ref。它可以像其他 ref 一样被访问以及修改,不过它能起到在父组件和当前变量之间的双向绑定的作用:
- 它的 .value 和父组件的 v-model 的值同步;
- 当它被子组件变更了,会触发父组件绑定的值一起更新。
这意味着你也可以用 v-model 把这个 ref 绑定到一个原生 input 元素上,在提供相同的 v-model 用法的同时轻松包装原生 input 元素:
child.vue
<script setup>
const model = defineModel()
</script>
<template>
<span>My input</span> <input v-model="model">
</template>
// app.vue
<script setup>
import Child from './Child.vue'
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1>{{ msg }}</h1>
<Child v-model="msg" />
</template>
底层机制
defineModel 是一个便利宏。编译器将其展开为以下内容:
- 一个名为 modelValue 的 prop,本地 ref 的值与其同步;
- 一个名为 update:modelValue 的事件,当本地 ref 的值发生变更时触发。
<!-- Child.vue -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="modelValue"
@input="emit('update:modelValue', $event.target.value)"
/>
</template>
<!-- Parent.vue -->
<template>
<ConfigGlobal>
<p :style="{'color':textColor}" >p标签</p>
{{ foo }}
<modelChild
:modelValue="foo"
@update:modelValue="$event => (foo = $event)"></modelChild>
</ConfigGlobal>
</template>
因为 defineModel 声明了一个 prop,你可以通过给 defineModel 传递选项,来声明底层 prop 的选项:
子组件
<script setup>
const model = defineModel({required:true, default: 1 }) // 对应 model 参数
</script>
<template>
<input v-model="model" type="text">
<!-- <input v-model="age" type="number"> -->
</template>
父组件
<script setup lang="ts">
const countModel = ref()
console.log(countModel.value,'countModel'); // undefined
</script>
<template>
<ConfigGlobal>
{{ countModel }}
<modelChild v-model="countModel"></modelChild>
</ConfigGlobal>
</template>
如果为 defineModel prop 设置了一个 default 值且父组件没有为该 prop 提供任何值,会导致父组件与子组件之间不同步。在下面的示例中,父组件的 countModel 是 undefined,而子组件的 model 是 1:
v-model 的参数
//子组件
<script setup>
const title = defineModel('title')
</script>
<template>
<input type="text" v-model="title" />
</template>
// 父组件
<script setup>
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
const bookTitle = ref('v-model argument example')
</script>
<template>
<h1>{{ bookTitle }}</h1>
<MyComponent v-model:title="bookTitle" />
</template>
不同属性绑定多个 v-model
父组件
<UserForm
v-model:username="user.name"
v-model:age="user.age"
/>
子组件
<script setup>
const username = defineModel('username') // 对应 username 参数
const age = defineModel('age') // 对应 age 参数
</script>
<template>
<input v-model="username" type="text">
<input v-model="age" type="number">
</template>
处理 v-model 修饰符
父组件
使用内置修饰符(如 .trim):
<Child v-model.trim="text" />
子组件
<script setup>
const [model, modifiers] = defineModel() // 解构出修饰符
// 根据修饰符调整值
const processedModel = computed({
get: () => model.value,
set: (value) => {
if (modifiers.trim) {
model.value = value.trim()
} else {
model.value = value
}
}
})
</script>
<template>
<input v-model="processedModel" />
</template>
- 使用自定义修饰符 .capitalize:
创建一个自定义的修饰符 capitalize,它会自动将 v-model 绑定输入的字符串值第一个字母转为大写
父组件
<Child v-model.capitalize="text" />
子组件
通过 set 选项处理修饰符逻辑
<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>
带参数的 v-model 修饰符
父组件
<UserForm
v-model:username.trim="user.name"
v-model:age.number="user.age"
/>
子组件
分别处理每个参数的修饰符:
<script setup>
const [username, usernameModifiers] = defineModel('username')
const [age, ageModifiers] = defineModel('age')
// 处理 username 的 trim 修饰符
const processedUsername = computed({
get: () => username.value,
set: (val) => {
username.value = usernameModifiers.trim ? val.trim() : val
}
})
// 处理 age 的 number 修饰符
const processedAge = computed({
get: () => age.value,
set: (val) => {
age.value = ageModifiers.number ? Number(val) : val
}
})
</script>
<template>
<input v-model="processedUsername" />
<input v-model="processedAge" type="number" />
</template>