正言
vue 是单向数据流,一般从父组件传到子组件的数据只允许在父组件里进行修改,子组件里只允许读取数据而不能进行数据修改,这样可以避免数据混乱,减少维护上带来的问题。不过 vue 官方也提供了两种方法来实现父子组件间的双向数据通信。
方式一
v-model 指令只能用于 input、textarea、单选框、复选框等类型的输入控件,也可以用于组件上,他是一个语法糖,可用于子父组件的双向通信
// home.vue
<template>
<div class="home">
<div>在父组件的值 ::: {{ testVal }}</div>
<hr>
<!-- 方式一 -->
<child v-model="testVal"></child>
</div>
</template>
<script>
import child from '@/views/child.vue'
export default {
name: 'home',
components: {
child
},
data () {
return {
testVal: '我是初始值'
}
}
}
</script>
// child.vue
<template>
<div>
<!-- v-model -->
<div>我是子组件 - v-model:::<input type="text" :value="value" @input="valChange" placeholder="子组件"></div>
</div>
</template>
<script>
export default {
name: 'child',
props: [
'value'
],
data () {
return {
}
},
methods: {
valChange (e) {
this.$emit('input', e.target.value)
}
}
}
</script>
初始页面,如下图:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL4IDN4QDOwUTM1EDMxkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
修改子组件 input 里的数据,如下图:
这样就可以实现从子组件改变数据,父组件也跟着一起改变。
v-model 是一个语法糖,上面的写法是下面写法的一个简版:
// home.vue
<template>
<div class="home">
<div>在父组件的值 ::: {{ testVal }}</div>
<hr>
<!-- v-model 原理 -->
<child :value="testVal" @input="value => testVal = value"></child>
</div>
</template>
<script>
import child from '@/views/child.vue'
export default {
name: 'home',
components: {
child
},
data () {
return {
testVal: '我是初始值'
}
}
}
</script>
从上面可以看出,v-model 其实是默认绑定一个属性 value,并且监听 input 事件(input 事件是 h5 出的,有兴趣的可以 点击 了解下)。v-model 的实现可以分以下步骤:
1)所以子组件里可以从 props 中获取到父组件传递过来 value (props: [ ‘value’])。
2)当子组件的输入框输入值时,会通过 @input=“valChange” 触发事件 valChange 从而执行 this.$emit(‘input’, e.target.value)。
3)父组件里的 @input=“value => testVal = value” 被触发,又修改了父组件里的 testVal 数据。实现了子组件和父组件数据同时修改。
方式二
.sync 修饰符不能和表达式一起使用,例如:v-bind:val.sync=“testVal + 'aa‘”,等于号右边是表达式,所以是无效的。同样的 .sync 也是一个语法糖。
// home.vue
<template>
<div class="home">
<div>在父组件的值 ::: {{ testVal }}</div>
<hr>
<!-- 方式二 -->
<child v-bind:val.sync="testVal"></child>
</div>
</template>
<script>
import child from '@/views/child.vue'
export default {
name: 'home',
components: {
child
},
data () {
return {
testVal: '我是初始值'
}
}
}
</script>
// child.vue
<template>
<div>
<!-- .sync -->
<div>我是子组件 - .sync:::<input type="text" :value="val" @input="valChange2" placeholder="子组件"></div>
</div>
</template>
<script>
export default {
name: 'child',
props: [
'val'
],
data () {
return {
}
},
methods: {
valChange2 (e) {
this.$emit('update:val', e.target.value)
}
}
}
</script>
这样也可以实现从子组件改变数据,父组件也跟着一起改变。
.sync 是一个语法糖,上面的写法是下面写法的一个简版:
// home.vue
<template>
<div class="home">
<div>在父组件的值 ::: {{ testVal }}</div>
<hr>
<!-- .sync 原理 -->
<child :val="testVal" @update:val="value => testVal = value"></child>
</div>
</template>
<script>
import child from '@/views/child.vue'
export default {
name: 'home',
components: {
child
},
data () {
return {
testVal: '我是初始值'
}
}
}
</script>
从上面可以看到 .sync 的实现过程是这样:
1)父组件自定义属性 val
2)子组件通过 props: [ ‘val’] 获取到父组件的数据
3)子组件通过 @input=“valChange2” 在 input 值发生变化时,触发 this.$emit(‘update:val’, e.target.value)
4)父组件通过 @update:val=“value => testVal = value” 监听事件,拿到数据后更新父组件数据。