v-model 是 Vue 提供的一个语法糖指令,用于在表单输入元素和组件上创建双向数据绑定。在表单元素中,v-model 会根据输入类型自动选择相应的方式来更新数据。
下面是常用的几种表单元素
1.输入框
v-model 等同于 :value 和 @change
<input v-model="str" />
<br />
<!-- 等同于-->
<input :value="str" @change="str = $event.target.value" />
2.复选框
v-model 等同于 :checked 和 @change
<input type="checkbox" v-model="isChecked" />
<!--等同于-->
<input
type="checkbox"
:checked="isChecked"
@change="isChecked = $event.target.checked"
/>
3.多个复选框
v-model 等同于 :checked 和 @change
<template>
<div class="v-model-container">
<!-- 多个复选框 -->
<input type="checkbox" v-model="isCheckedArray" value="Bob" />
<input type="checkbox" v-model="isCheckedArray" value="LiHua" />
<!--等同于-->
<input
type="checkbox"
:checked="isCheckedArray.includes('Bob')"
value="Bob"
@change="changeInput"
/>
<input
type="checkbox"
:checked="isCheckedArray.includes('LiHua')"
value="LiHua"
@change="changeInput"
/>
</div>
</template>
<script setup lang="ts">
let isChecked = ref(true)
let isCheckedArray = ref(['Bob'])
//Change方法需要手动实现
let changeInput = (event: any, value: any) => {
if (event.target.checked) {
isCheckedArray.value.push(value)
} else {
isCheckedArray.value = isCheckedArray.value.filter((item) => item !== value)
}
}
</script>
<style lang="scss" scoped>
</style>
4.单选框
v-model 等同于 :checked 和 @change
<input type="radio" v-model="selected" value="A" />
<input type="radio" v-model="selected" value="B" />
<!--等同于-->
<input
type="radio"
v-model="selected"
:checked="selected === 'A'"
@change="selected = 'A'"
value="A"
/>
<input
type="radio"
v-model="selected"
:checked="selected === 'B'"
@change="selected = 'B'"
value="B"
/>
5.下拉框
v-model 等同于 :value和 @change
<select v-model="selected">
<option value="A" label="A"></option>
<option value="B" label="B"></option>
</select>
<!--等同于-->
<select :value="selected" @change="selected = $event.target.value">
<option value="A" label="A"></option>
<option value="B" label="B"></option>
</select>
6.组件
v-model 在组件中使用时,它提供了一种简洁的方式来实现父子组件之间的数据双向绑定。
6.1 先看底层机制
子组件
一个名为 modelValue 的 prop,父组件传递给子组件的值;
一个名为 update:modelValue 的事件,使用emit触发来更新父组件传过来的值。
<template>
<div>
<h2>父组件传过来的值:{{ props.modelValue }}</h2>
<button @click="emit('update:modelValue', 2)">修改父组件的值</button>
</div>
</template>
<script setup lang="ts">
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<style lang="scss" scoped></style>
父组件
父组件中的v-model会被拆分为 :modelValue="count" 和 @update:modelValue="changeCount($event)"
<template>
<div>
<child :modelValue="count" @update:modelValue="changeCount($event)"></child>
</div>
</template>
<script setup lang="ts">
import child from '@/views/directive/components/child.vue'
let count: number = ref(0)
let changeCount = (val) => {
count.value += val
}
</script>
这样写可以直观的感受到vue单向数据流的机制 1.父组件传递数据(向下流动) 1.父组件通过props将数据传递给子组件 2.数据是只读的,子组件不能直接修改 2.子组件触发事件(向上通知) 1.子组件通过emit发送事件(如update:modelValue) 2.事件可以携带新数据($event) 3.父组件相应事件(更新数据) 1.父组件监听子组件事件,更新自己的数据 2.更新后的数据会重新通过props流向子组件
6.2 v-model基本使用
在vue3最新版本中提供了一个宏函数,并且官方也推荐使用该宏函数 defineModel()
父组件 父组件使用v-model绑定一个值
<template>
<div>
<child v-model="count"></child>
</div>
</template>
<script setup lang="ts">
import child from '@/views/directive/components/child.vue'
let count: number = ref(0)
</script>
子组件 defineModel() 返回的值是一个特殊的 ref。它可以像其他 ref 一样被访问以及修改,不过它能起到在父组件和当前变量之间的双向绑定的作用: 1. 它的 .value 和父组件的 v-model 的值同步; 2. 当它被子组件变更了,会触发父组件绑定的值一起更新; 3. 父组件值的变更也会自动同步到子组件; defineModel()等同于 6.1 中的prop 和 emit
<template>
<div>
<h2>父组件传过来的值:{{ model }}</h2>
<button @click="update">修改父组件的值</button>
</div>
</template>
<script setup lang="ts">
const model = defineModel()
function update() {
model.value+=2
}
</script>
<style lang="scss" scoped></style>
6.3 带参数的 v-model
Vue3中引入的一个重要特性,它允许你在单个组件上使用多个 v-model 绑定,每个绑定可以针对不同的数据属性。
不在参数的 v-model 默认绑定到 modelValue prop 和 update:modelValue事件
<template>
<div>
<h2>父组件</h2>
<p style="margin: 20px 0px">数量:{{ count }}</p>
<child v-model="count"></child>
<!-- 等价于-->
<child
:modelValue="count"
@update:modelValue="(newValue) => (count = newValue)"
></child>
</div>
</template>
带参数的v-model 允许你绑定指定的属性名
<template>
<div>
<h2>父组件</h2>
<p style="margin: 20px 0px">数量:{{ count }}</p>
<child v-model:count="count"></child>
<!-- 等价于-->
<child
:count="count"
@update:count="(newValue) => (count = newValue)"
></child>
</div>
</template>
这样看上去仿佛毫无意义,其实不然。它的作用主要在于允许一个组件同时控制多个数据属性,既可以在一个组件上实例上创建多个 v-model 双向绑定
比如下面的例子:可以修改单个属性,使绑定关系更加语义化和明确
父组件
<template>
<div>
<h1>父组件</h1>
<div style="margin: 20px 0px">
姓:
<span>{{ user.firstName }}</span>
名:
<span>{{ user.lastName }}</span>
邮箱:
<span>{{ user.email }}</span>
</div>
<child
v-model:firstName="user.firstName"
v-model:lastName="user.lastName"
v-model:email="user.email"
></child>
</div>
</template>
<script setup lang="ts">
import child from '@/views/directive/components/child.vue'
let count: number = ref(0)
let user = ref({
firstName: '张',
lastName: '三三',
email: '123456789@163.com',
})
</script>
子组件
<template>
<div>
<h2>父组件传过来的值</h2>
<div class="content">
姓:
<input v-model="firstName" />
名:
<input v-model="lastName" />
邮箱:
<input v-model="email" />
</div>
</div>
</template>
<script setup lang="ts">
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
const email = defineModel('email')
function update() {
// count.value += 2
}
</script>
<style lang="scss" scoped>
.content {
margin-top: 20px;
}
</style>
6.4 v-model修饰符
v-model 修饰符是Vue提供的一种便捷方式,用于在数据绑定到组件或原生元素时对数据进行预处理或特殊处理。 常用的内置修饰符:
.lazy:将input事件改为change事件,只在输入框失去焦点或按回车时更新数据
.trim:自动去除用户输入的首尾空白字符
.number:自动将用户输入转为数值类型 除了内置修饰符之外还有可以自定义修饰符
6.5 自定义修饰符
为了能够基于修饰符选择性地调节值的读取和写入方式,我们可以给 defineModel() 传入 get 和 set 这两个选项。这两个选项在从模型引用中读取或设置值时会接收到当前的值,并且它们都应该返回一个经过处理的新值
下面是一个例子,展示了如何利用 set 选项来应用 capitalize (首字母大写) 修饰符:
父组件
<template>
<div>
<child v-model.capitalize="text"></child>
</div>
</template>
<script setup lang="ts">
import child from '@/views/directive/components/child.vue'
let count: number = ref(0)
let text: string = ref('')
</script>
子组件 defineModel() 返回一个数组,包含两个元素:[model, modifiers]
model:用于双向绑定的响应式引用;
modifiers:包含所有使用的修饰符, 键是修饰符名称,值为 true,如果没有修饰符,返回空对象 {};
<template>
<div>
<input v-model="model" />
</div>
</template>
<script setup lang="ts">
const [model, modifiers] = defineModel({
// modifiers:{ capitalize: true }
set: (val) => {
if (modifiers.capitalize) {
return val.charAt(0).toUpperCase() + val.slice(1)
}
return val
},
})
// console.log(model, modifiers)
</script>
<style lang="scss" scoped>
</style>
6.6 带参数的 v-model 修饰符
在 Vue 3 中,带参数的 v-model 修饰符可以让你更精确地控制不同数据属性的处理方式。这种功能特别适合需要处理多个双向绑定值的复杂组件。
父组件
<template>
<div>
<child
v-model:first-name.capitalize="first"
v-model:last-name.uppercase="last"
v-model:age.number="age"
></child>
</div>
</template>
<script setup lang="ts">
import child from '@/views/directive/components/child.vue'
let first: any = ref(undefined)
let last: any = ref(undefined)
let age: any = ref(undefined)
</script>
子组件
处理修饰符
<template>
<div>
<label>First Name</label>
<input v-model="firstName" />
<label>last Name</label>
<input v-model="lastName" />
<label>age</label>
<input v-model="age" />
</div>
</template>
<script setup lang="ts">
const [lastName, lastNameModifiers] = defineModel('lastName')
const [firstName, firstNameModifiers] = defineModel('firstName', {
set(value) {
let processed = value
if (firstNameModifiers.capitalize) {
processed =
processed.charAt(0).toUpperCase() + processed.slice(1).toLowerCase()
}
if (firstNameModifiers.trim) {
processed = processed.trim()
}
return processed
},
})
const [age, ageModifiers] = defineModel('age', {
set(value) {
console.log(value)
let processed = value
if (ageModifiers.number) {
processed = Number(processed) || 0
}
if (ageModifiers.positive) {
processed = Math.abs(processed)
}
return processed
},
})
// console.log(model, modifiers)
</script>
<style lang="scss" scoped>
.content {
margin-top: 20px;
}
</style>