v-model

其实做了两个事情:

  • v-bind绑定value属性的值;
  • v-on绑定input事件监听到函数中,函数会获取最新的值赋值到绑定的属性中;

在原生表单元素中

1
<input v-model="serchText">

等价于:

1
<input :value="serchText" @input="serchText = $event.target.value"> 

在自定义组件中

在 Vue 2 中,自定义组件的 v-model 是一种常用的父子组件双向数据绑定方式。它的本质是对 value prop 和 input 事件的语法糖封装。

工作原理

  • v-model 默认会绑定到子组件的 value prop 上。

  • 当子组件需要修改这个值时,需要通过 $emit('input', newValue) 事件通知父组件更新。

子组件定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- child.vue -->
<template>
<input :value="value" @input="$emit('input', $event.target.value)" />
</template>

<script>
export default {
props: {
value: {
type: String,
default: ''
}
}
}
</script>

父组件使用:(这样,msg 就和 MyInput 组件的输入框实现了双向绑定。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<!-- v-model 默认等价于 :value="msg" @input="msg = $event" (这里的@input是子组件自定义事件) -->
<MyInput v-model="msg" />
</div>
</template>

<script>
import MyInput from './child.vue';
export default {
components: { MyInput },
data() {
return {
msg: '',
};
},
};
</script>

model

如果你想自定义 prop 名和事件名,可以用 model 选项:

1
2
3
4
5
6
7
export default {
model: {
prop: 'checked',
event: 'change'
},
props: ['checked']
}

这样父组件的 v-model 会变成 :checked=”xxx” @change=”xxx = $event”

例如:(父组件代码同上)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- child.vue -->
<template>
<input :value="fsllala" @input="$emit('test', $event.target.value)" />
</template>

<script>
export default {
model: {
prop: 'fsllala',
event: 'test'
},
props: {
fsllala: {
type: String,
default: ''
}
}
}
</script>

总结

  • Vue2 自定义组件的 v-model 默认绑定 value prop 和 input 事件。

  • 子组件通过 $emit(‘input’, newValue) 通知父组件更新。

  • 可以通过 model 选项自定义 prop 和事件名。

.sync 修饰符

在 Vue 2 中,.sync 是一个用于父子组件之间双向绑定 prop 的语法糖修饰符。

作用

  • 通常,父组件通过 prop 向子组件传递数据,子组件不能直接修改这个 prop。如果子组件需要修改父组件的数据,通常需要通过 $emit 事件通知父组件,然后父组件再修改传递给子组件的 prop。
  • .sync 修饰符简化了这个过程,让父组件可以更方便地实现 prop 的双向绑定。

使用方式

假设有一个子组件 child,它有一个 prop 叫 value:

1
2
<!-- 父组件模板 -->
<child :value="parentValue" @update:value="parentValue = $event"></child>

使用 .sync 后,可以这样写 (等价于上面那种写法):

1
<child :value.sync="parentValue"></child>

此时,子组件只需要这样触发事件,父组件的 parentValue 就会自动更新:

1
this.$emit('update:value', newValue)

总结

  • .sync 是 Vue 2 的语法糖,用于简化 prop 的双向绑定。
  • 实现原理是监听 update:propName 事件,并自动更新父组件的数据。
  • 在 Vue 3 中,推荐使用 v-model 代替 .sync。

相较于vue2

  • prop:value -> modelValue
  • 事件:input -> update:modelValue
  • v-bind.sync 修饰符和组件的 model 选项已移除
  • 新增 支持多个v-model
  • 新增 支持自定义 修饰符 Modifiers

即父组件中的 v-model="foo" 将被编译为:

1
2
3
4
5
<!-- Parent.vue -->
<Child
:modelValue="foo"
@update:modelValue="$event => (foo = $event)"
/>

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 父组件
<template>
<div>
<button @click="show = !show">开关{{show}}</button>
<VChild v-model="show" />
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import VChild from './child.vue';

const show = ref<boolean>(false);

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 子组件
<template>
<div v-if="propData.modelValue" class="dialog">
<div class="dialog-header">
<button @click="close">子组件关闭{{ propData.modelValue }}</button>
</div>
<div class="dialog-content">内容</div>
</div>
</template>

<script setup lang="ts">
type Props = {
modelValue: boolean;
};

const propData = defineProps<Props>();

const emit = defineEmits(['update:modelValue']);

const close = () => {
emit('update:modelValue', false);
};
</script>

v-model参数

默认情况下,使用v-model,相当于传递给组件一个modelValue,如上所示,我们可以修改这些名称,并将参数传递

1
<MyComponent v-model:title="bookTitle" />

这时候,组件中需要接收title值和update:title事件

1
2
3
4
5
6
7
8
9
<script setup lang="ts">
const props = defineProps({
title: {
type: String,
required: true
}
})
const emits = defineEmits(['update:title'])
</script>

defineModel

从 Vue 3.4 开始,推荐的实现方式是使用 defineModel() 宏:

defineModel() 返回的值是一个 ref。它可以像其他 ref 一样被访问以及修改,不过它能起到在父组件和当前变量之间的双向绑定的作用:

  • 它的 .value 和父组件的 v-model 的值同步;
  • 当它被子组件变更了,会触发父组件绑定的值一起更新。

父组件可以用 v-model 绑定一个值:

1
2
<!-- Parent.vue -->
<Child v-model="countModel" />
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 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>