vue是数据驱动视图更新的框架, 所以对于vue来说组件间的数据通信非常重要;我们常用的方式莫过于通过props传值给子组件,但是vue还有其他很多不常用的通信方式,了解他们,也许在以后在写代码的时候能给你带来更多的思路和选择。
prop/$emit
父组件通过prop
的方式向子组件传递数据,而通过$emit
子组件可以向父组件通信。
//Parent.vue
<template>
<div>
当前选中:{
{
current }}
<Child :list="list" @change="changeCurrent"></Child>
</div>
</template>
<script>
import Child from "./child";
export default {
data() {
return {
current: 0,
list: ["红楼梦", "水浒传", "三国演义", "西游记"]
};
},
components: {
Child },
methods: {
changeCurrent(num) {
this.current = num;
}
}
};
</script>
我们可以通过prop
向子组件传递数据;用一个形象的比喻来说,父子组件之间的数据传递相当于自上而下的下水管子,管子中的水就像数据,水只能从上往下流,不能逆流。这也正是Vue的设计理念之单向数据流。而prop
正是管道与管道之间的一个衔接口,这样水(数据)才能往下流。
//Child.vue
<template>
<div>
<template v-for="(item, index) in list">
<div @click="clickItem(index)" :key="index">{
{
item }}</div>
</template>
</div>
</template>
<script>
export default {
props: {
list: {
type: Array,
default: () => {
return [];
}
}
},
methods: {
clickItem(index) {
this.$emit("change", index);
}
}
};
</script>
在子组件中我们通过props对象定义了接收父组件值的类型和默认值,然后通过$emit()
触发父组件中的自定义事件。prop/$emit
传递数据的方式在日常开发中用的非常多,一般涉及到组件开发都是基于通过这种方式;通过父组件中注册子组件,并在子组件标签上绑定对自定义事件的监听。他的优点是传值取值方便简洁明了,但是这种方式的缺点是:
- 由于数据是单向传递,如果子组件需要改变父组件的props值每次需要给子组件绑定对应的监听事件。
- 如果父组件需要给孙组件传值,需要子组件进行转发,较为不便。
.sync修饰符
有些情况下,我们希望在子组件能够“直接修改”父组件的prop值,但是双向绑定会带来维护上的问题;vue提供了一种解决方案,通过语法糖.sync修饰符。
.sync修饰符在 vue1.x
的时候曾作为双向绑定功能存在,即子组件可以修改父组件中的值。但是它违反了单向数据流的设计理念,所以在 vue2.0
的时候被干掉了。但是在 vue2.3.0+
以上版本又重新引入了。但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的v-on
监听器。说白了就是让我们手动进行更新父组件中的值了,从而使数据改动来源更加的明显。
//Parent.vue
<template>
<div>
<Child :msg.sync="msg" :num.sync="num"></Child>
</div>
</template>
<script>
import Child from "./child";
export default {
name: "way2",
components: {
Child
},
data() {
return {
msg: "hello every guys",
num: 0
};
}
};
</script>
我们在Child组件传值时给每个值添加一个.sync修饰,在编译时会被扩展为如下代码:
<Child :msg="msg" @update.msg="val => msg = val" :num.sync="num" @update.num="val => num = val"></Child>
因此子组件中只需要显示的触发update的更新事件:
//Child.vue
<template>
<div>
<div @click="clickRevert">点击更新字符串:{
{
msg }}</div>
<div>当前值:{
{
num }}</div>
<div @click="clickOpt('add')" class="opt">+</div>
<div @click="clickOpt('sub')" class="opt">-</div>
</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
default: ""
},
num: {
type: Number,
default: 0
}
},
methods: {
clickRevert() {
let {
msg } = this;
this.$emit("update:msg",msg.split("").reverse().join(""));
},
clickOpt(type = "") {
let {
num } = this;
if (type == "add") {
num++;
} else {
num--;
}
this.$emit("update:num", num);
}
}
};
</script>
这种“双向绑定”的操作是不是看着似曾相识?是的,v-model本质上也是一种语法糖,只不过它触发的不是update方法而是input方法;而且v-model没有.sync来的更加灵活,v-model只能绑定一个值。
总结:.sync修饰符优化了父子组件通信的传值方式,不需要在父组件再写多余的函数来修改赋值。
$attrs和$listeners
当需要用到从A到C的跨级通信时,我们会发现prop传值非常麻烦,会有很多冗余繁琐的转发操作;如果C中的状态改变还需要传递给A,使用事件还需要一级一级的向上传递,代码可读性就更差