Vue父子组件间双向数据绑定:v-model与dineModel详解

本文最后更新于 2024年2月7日 上午

在Vue开发中,父子组件之间经常需要进行数据传递和交互。使用v-model指令可以很容易地在父子组件之间实现数据的双向绑定。本文将详细介绍Vue父子组件使用v-model的方法和注意事项。

最近有一个小发现,Vue3.3版本开始,v-model这个语法糖有了些许调整;这里就来说一下。

语法糖

v-model

要解释什么是v-model,可能要先解释一下Vue中的子父组件传值:

  • props: 子组件用来接收从父组件传递的数据;在Vue中,父组件到子组件的数据传递可以通过props实现的。父组件可以通过props向下传递数据给子组件,子组件通过定义props选项来接收来自父组件的数据。这种数据流是单向的,意味着子组件不能直接修改props中的数据,因为这将导致父子组件间的数据状态不一致性,Vue中会发出警告。
  • emit: 子组件用来向父组件发送消息;当子组件需要将数据变化通知父组件时,它可以使用$emit来触发一个事件,并将数据作为事件的参数传递给父组件。父组件监听这个事件,并在回调函数中处理接收到的数据。通常用于子组件向父组件发送信号告知某些动作已经发生,或者数据需要更新。

嘿嘿

在这样的机制下,Vue提供了v-model语法糖指令来简化双向数据绑定,也就是在子组件内,使用特定的props属性和emit事件,就可以使用v-model简化父组件内的传值流程,进而实现数据双向绑定

只是在Vue2、Vue3.0~Vue3.3和Vue3.3+内有所不同。

Vue2

在Vue2中,使用v-model,会自动使用名称为value的props属性,和使用名称为input的监听事件。进而使用这个特性,封装自己的组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<input
v-model="localMessage"
/>
</template>

<script>
export default {
props: ['value'],

computed: {
// 使用计算属性,实现子组件内的内容更新会自动更新父组件
localMessage: {
get() {
return this.value
},
set(newValue) {
this.$emit('input', newValue)
}
}
}
}
</script>

这样父组件内部,可以有原本<child :value="username" @input="username=$event" />,简化为<child v-model="username" />

当然,有些人可能会使用$refs,通过父组件调用子组件内部公开的方法来完成数据的同步,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<child ref="childComponent" />
<button @click="callChildMethod">调用子组件方法</button>
</div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
components: {
ChildComponent
},
methods: {
callChildMethod() {
this.$refs.childComponent.methodName();
}
}
}
</script>

但是我认为,$ref作为获取子组件DOM元素、访问子组件实例的工具钩子;如果是封装组件,特别是表单类别、渲染模块,那么使用v-model更优雅,不用考虑还需要$refs二次调用的情况。

Vue3.0~Vue3.3

但是,Vue3版本开始有所不同。在[Vue3.0, Vue3.3)版本之间,为了更好地支持组合式 API 和一致性,Vue 3引入了modelValue@update的语法糖,以取代先前的value@input:

1
<input v-model="model"/>

[Vue3.0, Vue3.3)之间,等于:

1
2
3
<input
v-model="modelValue"
@update:modelValue="updateModelValue"/>

具体的使用样例:

1
2
3
// Parent.vue

<Child v-model="name" label="Name" />

子组件实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Child.vue

<template>
<input
class="input"
type="text"
:placeholder="props.label"
:value="props.modelValue"
v-on:input="updateValue($event.target.value)"
/>
</template>

<script setup>

const props = defineProps({
modelValue: String
})

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

const updateValue = (value) => {
emit('update:modelValue', value)
}
</script>

但是,在Vue3.3版本开始(稳定版本应该是Vue3.4)。有有所不同。

Vue3.4

Vue版本<3.3的情况下,就如上文一样,v-modelmodelValue@update的语法糖;甚至在Vue2.x时代,是value@input的语法糖。

Vue3.4代号”灌篮高手”

你当然可以在Vue3.4时候,继续使用modelValue@update的语法糖,但是Vue3.4开始,引入一种更优雅的方式:defineModel,参考自: https://blog.vuejs.org/posts/vue-3-4:

1
2
3
4
5
6
Today we're excited to announce the release of Vue 3.4 "🏀 Slam Dunk"!

This release includes some substantial internal improvements - most notably a rewritten template parser that is 2x faster, and a refactored reactivity system that makes effect triggering more accurate and efficient. It also packs a number of quality-of-life API improvements, including the stabilization of defineModel and a new same-name shorthand when binding props.

今天,我们很高兴地宣布 Vue 3.4 “🏀灌篮高手”的发布!
此版本包括一些实质性的内部改进 - 最引人注目的是重写的模板解析器,速度提高了 2 倍,以及重构的反应系统,使效果触发更加准确和高效。它还包含许多生活质量 API 改进,包括绑定 prop 时的 defineModel 稳定性和新的同名速记。

具体在setup()内使用,非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 声明 "modelValue" prop,由父组件通过 v-model 使用
const model = defineModel()
// 或者:声明带选项的 "modelValue" prop
const model = defineModel({ type: String })

// 在被修改时,触发 "update:modelValue" 事件
model.value = "hello"

// 声明 "count" prop,由父组件通过 v-model:count 使用
const count = defineModel("count")
// 或者:声明带选项的 "count" prop
const count = defineModel("count", { type: Number, default: 0 })

const inc = () => {
// 在被修改时,触发 "update:count" 事件
count.value++
}

也就是,使用defineModel,你就不需要强制在子组件内定义modelValue@update了。这个新的语法糖,进一步简化了代码。

如果你想获取v-model的描述,比如定义首字母大写:

1
<Child v-model.capitalize="username"/>

那么用defineModel的形式,可以这样:

1
2
3
4
5
6
7
8
// Child.vue
<script setup>
const [modelValue, modelModifiers] = defineModel()

if (modelModifiers.capitalize) {
modelValue = modelValue.charAt(0).toUpperCase() + modelValue.slice(1);
}
</script>

可以看到,Vue3.4的新特性,还是非常好用的。

Support

制作教程不易,如果热心的小伙伴,想支持创作,可以加入我们的「爱发电」电圈(还可以解锁远程协助、好友位😃):

当然,也欢迎在B站或YouTube上关注我们:

更多:

END

在本文中,我们深入探讨了在Vue框架中,父子组件之间数据通信的核心机制,并特别关注了v-model指令的功能与在Vue版本迭代中的变化。通过对propsemit机制的解析,我们了解了如何在组件间单向传递数据以及如何通知父组件子组件的数据变化。

随着Vue3起的变更,v-model的包装参数的名称更加合理,特别是在Vue3.4及以后的版本中引入的defineModel配置方式更加简洁和优雅。

希望文章对你有用嗷。



Vue父子组件间双向数据绑定:v-model与dineModel详解
https://www.mintimate.cn/2024/01/17/vModelVue/
作者
Mintimate
发布于
2024年1月17日
许可协议