前言
这里说的数据双向绑定,指的是 vue 父子组件的数据双向绑定,而不是 vue 的数据双向绑定原理(数据与视图的双向绑定更新)
关于子组件不能修改父组件穿入的props数据,官方这样解释:
"注意在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果在子组件中直接修改父组件传递的对象或数组,会影响到父组件的状态,违背了单向数据流的规则。"
这意味着如果子组件直接修改了 props 数据,父组件中的相应数据也会被修改,可能会导致非预期的结果和调试困难。
但是很多封装组件都是有这方面的需求,不然来回的回调事件会让代码更加臃肿繁琐,那么如何实现父子组件数组的双向绑定,有如下几种方式:
vue2
v-model
Vue 2 中 提供了 一个v-model
的指令,实际上是一个语法糖,
是对 :value
和 @input
两个指令的结合使用,简化了常见的表单元素绑定代码
相当于以下的缩写
html
<input :value="message" @input="message = $event.target.value" type="text">
那在子组件中该如何使用最为方便呢?
可以使用vue的计算属性,当计算属性发生改变时更新value值,这样 子组件只需要直接操作这个计算属性值就可以了,具体代码如下
html
<template>
<div>
<div>父组件传过来的值:{{ value }}</div>
<div>子组件内的计算属性:{{ childrenValue }}</div>
<button @click="btn('add')">子组件按钮+1</button>
<button @click="btn('delete')">子组件按钮-1</button>
</div>
</template>
<script>
export default {
props: {
value: {
type: Number,
default: 0
}
},
computed: {
// 通过get/set函数 当计算属性改变时触发$emit更新父组件的值
childrenValue: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
}
},
methods: {
btn(type) {
switch (type) {
case 'add':
this.childrenValue++
break;
case 'delete':
this.childrenValue--
break
}
}
}
}
</script>

v-model 指令默认使用的键名为 value 和 input,意思就是相当于传入props value,和发出 $emit('input', '修改的值')
如有需要,这个使用的键名是可以更改的
在子组件中,可以通过model属性来进行更改,例如
js
export default {
model: {
prop: "visible", // 默认接收键名更改
event: "visibleChange", // 默认发起事件键名更改
},
props: {
visible: { // v-mode传入的值
type: Boolean,
default: false
}
},
methods: {
setValue() {
this.$emit("visibleChange", false); // 修改 visible 值
}
}
}
sync 修饰符
sync
是一个修饰器,用于简化父子组件之间的双向数据绑定。它通过自动生成一个名为 update:propertyName
的自定义事件,实现父组件能够直接修改子组件的属性值。
html
<myComponent :value.sync="valueText" />
相当于如下的简写
html
<myComponent v-bind:title="valueText" v-on:update:title="valueText=$event" />
所以在子组件直接调用 $emit('update:title', '更新的值') 就可以更新父组件的值
与v-model不同的是,sync修饰符可以用于多个props传值,并不局限于一个
逻辑与之前类似
html
<myComponent :value1.sync="valueText1" :value2.sync="valueText2" />
html
<template>
<div>
<div>父组件传过来的value1:{{ value1 }}</div>
<div>父组件传过来的value2:{{ value2 }}</div>
<button @click="btn('childrenValue1')">子组件修改value1</button>
<button @click="btn('childrenValue2')">子组件修改value2</button>
</div>
</template>
<script>
export default {
props: {
value1: {
type: Number,
default: 0
},
value2: {
type: Number,
default: 0
}
},
computed: {
// 通过get/set函数 当计算属性改变时触发$emit更新父组件的值
childrenValue1: {
get() {
return this.value1
},
set(val) {
this.$emit('update:value1', val)
}
},
childrenValue2: {
get() {
return this.value2
},
set(val) {
this.$emit('update:value2', val)
}
},
},
methods: {
btn(target) {
this[target]++
}
}
}
</script>

vue3
v-model
在vue3中,剔除了 sync 修饰符,v-model
指令不再是一个特殊的指令,而被视为一种语法糖,默认传入props键名为 modelValue
并支持使用多个v-model,但是要自定义键名,如
html
<myComponent v-model="valueText" v-model:value2="valueText2" />
在组件内同样通过 emit('update:modelValue', '修改的值') 来进行更新父组件的值
示例如下,案例使用了ts和script setup模式
html
<template>
<div>value1值:{{ value1 }}<button @click="btn('value1')">按钮1</button></div>
<div>value2值:{{ value2 }}<button @click="btn('value2')">按钮2</button></div>
</template>
<script setup lang="ts">
import { toRefs } from 'vue'
interface Props {
value1: number
value2: number
}
const props = withDefaults(defineProps<Props>(), { // props
value1: 0,
value2: 0
})
const emit = defineEmits(['update:value1', 'update:value2']) // 定义事件
const { value1, value2 } = toRefs(props) // 引用解构出value1、value2
// 按钮操作
function btn(type: string) {
console.log(value1, value2)
switch (type) {
case 'value1':
emit('update:value1', value1.value + 1) // 更新父组件值
break;
case 'value2':
emit('update:value2', value2.value + 1) // 更新父组件值
break
}
}
</script>
使用组件
html
<template>
<myComponent v-model:value1="value1Text" v-model:value2="value2Text" />
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import myComponent from './components/VMdel.vue'
const value1Text = ref<number>(0)
const value2Text = ref<number>(0)
</script>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
效果如下,在子组件内可以更新父组件的值

本文完!如有不足或缺漏之处请及时指出,感谢!!