双向绑定 v-model
v-model:value="值"
可简写为v-model="值"
,用于双向绑定 [表单元素] 的信息- 双向绑定:表单元素的
value
attribute 的值 ↔ data 中对应的值 - 本质上,v-model 是由 v-bind 配合 input 事件实现的
① v-bind 绑定 value 属性、② 在 input 事件的回调函数中更新 value 的值
文本 input [text]
<div id="app">
<p>Message is: {{ msg }}</p>
<!-- 给 input 标签元素设置 v-model 属性 -->
<input
v-model="msg"
type="text"
/>
</div>
let vm = new Vue({
el: '#app',
data: { msg: 'superman' },
});
上例中,input 的默认值为 data 中的 msg,更新 input 的 value 值 → msg 也会被更新 → p 显示的内容也会被更新
多行文本 textarea
- 对于文本域标签
<textarea>
,用法与 type=“text” 的 input 标签一样 - 虽然
<textarea>
是双标签,但<textarea> {{msg}} </textarea>
并不会生效,要设置v-model
属性来替代
<textarea v-model="msg"></textarea>
单选框 input [radio]
-
因为 v-model 绑定的是 value 值
所以,我们需要设置
value
属性;如果没有设置value
属性,则获取到的值为null
<input type="radio" id="male" value="male" v-model="sex" />
<label for="male">male</label>
<br />
<input type="radio" id="female" value="female" v-model="sex" />
<label for="female">female</label>
<br />
<span>sex: {{ sex }}</span>
let vm = new Vue({
el: '#app',
data: {
sex: '',
// sex: "male" // 设置默认值 'male',默认选中 male 选项
},
});
复选框 input [checkbox]
- 没有配置
value
属性,收集的是 checkbox 的checked
状态,所以会收集到布尔值
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{checked}}</label>
let vm = new Vue({
el: '#app',
data: { checked: true }, // 绑定 [布尔值]
});
- 配置了
value
属性,绑定 [非数组],收集的也还是 checkbox 的checked
状态
所以也还是会收集到布尔值 →ture
-全选、false
-全不选
篮球
<input type="checkbox" value="篮球" v-model="checked" />
足球
<input type="checkbox" value="足球" v-model="checked" />
网球
<input type="checkbox" value="网球" v-model="checked" />
<p>{{checked}}</p>
- 配置了
value
属性,绑定 [数组],才能收集到多选框的value
值
篮球
<input type="checkbox" value="篮球" v-model="checked" />
足球
<input type="checkbox" value="足球" v-model="checked" />
网球
<input type="checkbox" value="网球" v-model="checked" />
<p>{{checked}}</p>
let vm = new Vue({
el: '#app',
data: {
checked: [], // 绑定到数组
},
});
- 能获取到多个值的表单元素,都应该绑定 [数组],以存储多个数据
选择框 select > option
- 对于下拉菜单,推荐提供一个值为空的禁用选项,作为默认显示选项
<select v-model="selected">
<option
disabled
value=""
>
国家
</option>
<option value="China">中国</option>
<option value="America">美国</option>
<option value="Japan">日本</option>
</select>
<span>Selected: {{ selected }}</span>
let vm = new Vue({
el: '#app',
data: {
selected: '',
// selected: "China" // 设置默认值
},
});
- 如果
option
标签不设置value
属性,则获取到的是option
标签的内容
<select v-model="selected">
<option
disabled
value=""
>
国家
</option>
<option>中国</option>
<option>美国</option>
<option>日本</option>
</select>
<span>Selected: {{ selected }}</span>
- 多选时,绑定到一个数组
<select multiple v-model="selected">
<option
disabled
value=""
>
国家
</option>
<option>中国</option>
<option>美国</option>
<option>日本</option>
</select>
<span>Selected: {{ selected }}</span>
let vm = new Vue({
el: '#app',
data: {
selected: [], // 绑定到数组
},
});
标签修饰符
.lazy
:在change
事件之后同步,即失焦再同步(默认input
事件之后同步,即一边写一边同步).trim
:过滤首尾空白字符.number
:将输入数据使用parseFloat()
转为 Number 类型;若这个值无法被parseFloat()
解析,则会返回输入的字符串
<input type="text" v-model.lazy="username" />
<span>{{ username }}</span>
<br />
<input type="text" v-model.trim="password" />
<span>{{ password }}</span>
<br />
<!-- type="number" 能限制 input 只能输入数字 -->
<input type="number" v-model.number="age" />
<span>{{ age }}</span>
<br />
<!-- v-model.number="age" 会将数据转为 Number 类型 -->
<input type="text" v-model.number="height" />
<span>{{ height }}</span>
表单提交事件
submit
提交表单时触发.prevent
阻止默认行为
<form @submit.prevent="console.log('submit successfully')"></form>
Vue2 组件使用 v-model
v-model
其实是 v-bind:value
+ v-on:input
的语法糖
<template>
<div>
{{ demoText }}
<input
:value="demoText"
@input="ev => (demoText = ev.target.value)"
/>
<input v-model="demoText" />
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
demoText: 'demoText',
};
},
};
</script>
同理,在自定义组件上使用:
<template>
<div>
{{ demoText }}
<HelloWorld
:value="demoText"
@input="val => (demoText = val)"
/>
<HelloWorld v-model="demoText" />
</div>
</template>
<script>
import HelloWorld from '@/components/HelloWorld.vue';
export default {
name: 'App',
components: { HelloWorld },
data() {
return {
demoText: 'demoText',
};
},
};
</script>
需要注意的是:自定义组件上的 :value
变成了自定义属性,@input
变成了自定义事件
<template>
<input
type="text"
:value="value"
@input="$emit('input', $event.target.value)"
/>
<!-- $emit('input', $event.target.value) 表示触发自定义事件, 传入 $event.target.value 作为参数-->
</template>
<script>
export default {
name: 'HelloWorld',
props: ['value'], // 接收自定义属性
};
</script>
.sync 修饰符
上例可以优化成:
<template>
<div>
{{ demoText }}
<HelloWorld
:title="demoText"
@update:title="demoText = $event"
/>
</div>
</template>
<template>
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>
<script>
export default {
name: 'HelloWorld',
props: ['title'],
};
</script>
这种写法可以使用语法糖
<template>
<div>
{{ demoText }}
<HelloWorld v-bind:title.sync="demoText" />
</div>
</template>
注意:带有 .sync
修饰符的 v-bind
不能和表达式一起使用;eg: v-bind:title.sync="doc.title + '!'"
是无效的
- 当我们用一个对象同时设置多个 prop 的时候,也可以将这个
.sync
修饰符和v-bind
配合使用
<HelloWorld v-bind.sync="doc"></HelloWorld>
这样会把 doc
对象中的每一个 property (如 title
) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on
监听器
注意:将 v-bind.sync
用在一个字面量的对象上,eg: v-bind.sync="{ title: doc.title }"
,是无法正常工作的
因为在解析一个像这样的复杂表达式的时候,有很多边缘情况需要考虑
Vue3 组件使用 v-model
- 在普通标签元素上使用
v-model
<template>
{{ textDemo }}
<input v-model="textDemo" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
const textDemo = ref('textDemo');
</script>
上例等价于
<template>
{{ textDemo }}
<input
:value="textDemo"
@input="textDemo = ($event.target as any).value"
/>
</template>
- 在自定义组件上使用
v-model
<template>
<HelloWorld v-model="textDemo" />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import HelloWorld from '@/components/HelloWorld.vue';
const textDemo = ref('Vue.js');
</script>
上例等价于
<template>
<HelloWorld
:modelValue="textDemo"
@update:modelValue="(newValue:any) => (textDemo = newValue)"
/>
</template>
此时,子组件内需要做两件事:
① 将原生 input
元素的 value
attribute 绑定到 modelValue
prop
② 当原生的 input
事件触发时,触发一个携带了新值的 update:modelValue
自定义事件
<template>
{{ modelValue }}
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', ($event.target as any).value)"
/>
</template>
<script lang="ts" setup>
defineProps<{ modelValue: string }>(); // 接收 `modelValue` prop
defineEmits<{ (e: 'update:modelValue', val: string): void }>(); // 接收 `update:modelValue` 自定义事件
</script>
v-model 的参数
- 默认情况下,
v-model
在组件上都是使用modelValue
作为 prop,并以update:modelValue
作为对应的事件 - 可以给
v-model
指定一个参数来更改这些名字
<template>
<!-- 设置 v-model 的参数 title -->
<HelloWorld v-model:title="textDemo" />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import HelloWorld from '@/components/HelloWorld.vue';
const textDemo = ref('Vue.js');
</script>
<template>
{{ title }}
<input
type="text"
:value="title"
@input="$emit('update:title', ($event.target as any).value)"
/>
</template>
<script lang="ts" setup>
defineProps<{ title: string }>(); // v-model 的参数名 title 作为 prop
defineEmits<{ (e: 'update:title', val: string): void }>(); // 自定义事件名也变成了 update:title
</script>
v-model 的修饰符
- v-model 有一些内置的修饰符 (
.trim
、.number
、.lazy
) - 若使用了自定义修饰符,可通过
modelModifiers
prop 在组件内访问该自定义修饰符 - 若 v-model 携带着参数,eg:
v-model:title
,则 prop 为titleModifiers
<template>
{{ title }}
<input
type="text"
:value="title"
@input="$emit('update:title', ($event.target as any).value)"
/>
</template>
<script lang="ts" setup>
const props = withDefaults(
defineProps<{
title: string;
titleModifiers: {}; // 接收 prop titleModifiers
}>(),
{
title: '',
titleModifiers: () => ({}), // 设置默认值为空对象
}
);
defineEmits<{ (e: 'update:title', val: string): void }>();
console.log(props.titleModifiers); // { capitalize: true }
</script>
上例中,props.titleModifiers
的值为 { capitalize: true }
,是因为 v-model 使用了修饰符 capitalize
。
有了这个 prop,就可以检查 modelModifiers
对象的 key,并编写一个处理函数以改变抛出的值
<template>
{{ title }}
<input
type="text"
:value="title"
@input="emitValue"
/>
</template>
<script lang="ts" setup>
const props = withDefaults(
defineProps<{
title: string;
titleModifiers: {};
}>(),
{
title: '',
titleModifiers: () => ({}),
}
);
const emit = defineEmits<{ (e: 'update:title', val: string): void }>();
function emitValue(e: any) {
let value = e.target.value;
if ((props.titleModifiers as any).capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1);
}
emit('update:title', value);
}
</script>