v-model 语法糖
Vue2 中的实现
原理
其核心实现分两步:
- 使用 v-bind 绑定数据
- 触发指定事件修改 v-bind 绑定的数据
html
<input v-bind:value="something" v-on:input="something = $event.target.value">
等价于
html
<input v-model="something">
组件上实现
由于在 input 上自带了 input 事件,所以我们能轻易使用 v-model 语法糖实现双向绑定。那么如果想要在组件上实现 v-model, 子组件必须向父组件抛出 input 事件。
vue
父组件
<template>
<div>
<ChildTpl v-model="count" />
<!-- 等价于 -->
<ChildTpl :value="count" @input="value = $event" />
</div>
</template>
vue
子组件
<script>
export default {
props:{
value:{
default:0,
}
},
methods: {
changeValue(){
this.$emit('input', this.value+1) // 向父组件抛出 input 事件和最新的值
}
}
}
</script>
自定义值和事件
当然上面的方式只支持 value 值和 input 事件,如果我们想自定义属性和事件的话,子组件需要使用到 model 属性
vue
子组件
<script>
export default {
model: {
prop: "customParams", // 将默认value属性改为customParams
event: "change", // 将默认input事件改为change
},
props: {
customParams: {
type: Number,
default: 0,
},
},
methods: {
changeValue() {
this.$emit("change", this.customParams + 1);
},
},
};
</script>
vue
父组件
<template>
<ChildTpl v-model="count" />
</template>
实现多个v-model
首先明确,Vue2 是不支持实现多个 v-model 语法的。不过 vue 2.3.0 时新增了 sync 修饰符,我们可以借此来实现多个 v-model
vue
子组件
<script>
export default {
props: {
title: {
type: String,
default: '',
},
name: {
type: String,
default: '',
}
},
methods: {
changeValue() {
this.$emit("update:title", newTitle)
this.$emit("update:name", newName);
},
},
};
</script>
vue
父组件
<template>
<ChildTpl :title.sync="parentTitle" :name.sync="parentName" />
</template>
注意父组件中不能使用 v-model ,只能是 v-bind:---.sync 这样的写法。
TIP
注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是无效的)。
Vue3 中的实现
Vue3 中的实现和 Vue2 中差不多,其核心在于在组件上的差别。
input 上实现
依然是通过 v-bind 绑定属性,然后通过 input 事件来修改绑定的值。
html
<input :value="searchText" @input="searchText = $event.target.value" />
在组件上实现
父组件
vue
<template>
<ChildTpl v-model="count" />
<!-- 等价于 -->
<ChildTpl :modelValue="count" @update:modelValue="count = $event" />
</template>
子组件
vue
<template>
<button @click="changeValue">count+1</button>
</template>
<script lang="ts" setup>
const emits = defineEmits(["update:modelValue"])
const props = defineProps({
modelValue: {
type: Number,
default: 0,
},
});
const changeValue = () => {
emits("update:modelValue", props.modelValue + 1)
}
</script>
与 Vue2 的区别在于:
- 通过 props 来指定 modelValue 绑定的属性
- 通过指定的 update:modelValue 事件名来触发事件
自定义值和事件
默认情况下,v-model 在组件上都是使用 modelValue 作为 prop,并以 update:modelValue 作为对应的事件。我们可以通过给 v-model 指定一个参数来更改这些名字
父组件:
vue
<template>
<ChildTpl v-model:num="count" />
<!-- 等价于 -->
<ChildTpl :num="count" @update:num="count = $event" />
</template>
子组件:
vue
<template>
<button @click="changeValue">加一</button>
</template>
<script lang="ts" setup>
const emits = defineEmits(["update:num"])
const props = defineProps({
num: {
type: Number,
default: 0,
},
})
const changeValue = () => {
emits("update:num", props.num + 1);
}
</script>
支持多个 v-model
父组件:
vue
<template>
<ChildTpl v-model:first-name="first" v-model:last-name="last" />
<!-- 等价于 -->
<ChildTpl
:first-name="first"
:last-name="last"
@update:first-name="first = $event"
@update:last-name="last = $event"
/>
</template>
子组件:
vue
<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>
<script lang="ts" setup>
const emits = defineEmits(['update:firstName', 'update:lastName'])
const props = defineProps({
firstName: {
type: String,
default: '',
},
lastName: {
type: String,
default: '',
},
});
</script>