前提:
vuejs在跨组件传递数据的时候,常用的有以下几个方式:
- 子组件用props接收父组件的数据,子组件内部值改变的时候通过$emit事件告知父组件。
- Vuex,不仅是父子组件,整个vue工程的所有页面都可以在store中赋值,并且取值,达到数据传递的效果。
- Event Bus,事件总线,通过new Vue()得到一个实例,在各自需要的地方引进该实例进行事件监听$on和事件触发$emit。必须保证先有$on之后,再由$emit触发同名事件之后才能正常对事件做操作。
- 多层级的组件,由外层向里层传递数据,由里层向外层触发事件,主要场景是中间层级不关心传递给下一级的属性,以及不关心下一级触发的指定事件。
场景:
父组件有指定的属性需要传递给孙子组件,孙子组件注册对应的事件要告知父组件。作为中间层级的子组件,如果按照第一种方式,需要将自己从父组件接收到的数据主动传递到孙子组件。以及主动接收孙子组件的事件,并告知给父组件。在传递多个属性和事件的时候会造成代码冗余。
在多层级的组件场景中,从2.4.0版本开始,vuejs提供了穿透属性和事件的写法,以下是官方文档截图:


实际使用:
那么,在传递的过程当中,如果子组件和孙子组件拥有同样的属性以及触发了相同的事件。在父组件层面会有什么响应效果呢?
- 父组件代码:
<template>
<div class="parent-page">
<h3>这里是parent,以下是child信息</h3>
<child
child-name="小冰原"
real-name="小小冰原"
@save-money="onSaveMoney"
@grand-son-request="onGrandSonRequest"
@child-request="onChildRequest" />
</div>
</template>
<script>
import Child from "./child";
export default {
name: "parent",
components: {Child},
data() {
return {
};
},
created() {
},
methods: {
onSaveMoney(information) {
console.log(`这里是parent:后代赚钱了,${information.alias}赚了${information.amount}`);
},
onGrandSonRequest() {
console.log('这里是parent,要响应grand-son的request');
},
onChildRequest() {
console.log('这里是parent,要响应child的request');
}
}
}
</script>
<style scoped>
.parent-page {
width: 1000px;
margin: 0 auto;
padding: 30px;
}
</style>- 子组件代码:
<template>
<div class="child-page">
<div>
child name: {{ childName }}
</div>
<div class="btns">
<el-button @click="saveMoney">儿子赚钱</el-button>
<el-button @click="request">儿子请求</el-button>
</div>
<div>
<h3>我是child,以下是grand-son:</h3>
<div class="grand-son-div">
<grand-son v-bind="$attrs" v-on="$listeners" />
</div>
</div>
</div>
</template>
<script>
import GrandSon from "./grand-son";
export default {
name: "child",
components: {GrandSon},
props: {
childName: {
type: String,
default: ''
},
},
data() {
return {};
},
created() {
},
methods: {
saveMoney() {
console.log('儿子赚钱了,要告诉parent');
this.$emit('save-money', {alias: '儿子', amount: 500});
},
request() {
console.log('儿子要求帮助');
this.$emit('child-request');
}
}
}
</script>
<style scoped>
.grand-son-div {
background-color: antiquewhite;
padding: 10px;
}
.child-page {
background-color: azure;
padding: 10px;
}
</style>- 孙子组件代码:
<template>
<div>
<div>
grand-son childName: {{ childName }}
</div>
<div>
grand-son realName: {{ realName }}
</div>
<div class="btns">
<el-button @click="saveMoney">孙子赚钱</el-button>
<el-button @click="request">孙子请求</el-button>
</div>
</div>
</template>
<script>
export default {
name: "grand-son",
props: ['childName', 'realName'],
data() {
return {};
},
created() { },
methods: {
saveMoney() {
console.log('孙子赚钱了,要告诉parent组件');
this.$emit('saveMoney', {alias: '孙子', amount: 100});
},
request() {
console.log('孙子要求帮助');
this.$emit('grand-son-request');
}
}
}
</script>
<style scoped>
</style>- 页面效果:

- 按键触发的代码效果:

总结:
- 父组件往下传值,如果子组件已经匹配了,那么孙子组件将得不到该配置。
- 如果子组件没对应的配置,将有$attrs将其传递到孙子组件,如果有匹配的就直接使用该配置。
- 孙子组件往上传递事件,如果该事件名称与子组件名称重复,那么该事件无法传递到父组件。
- 如果孙子组件触发的事件在子组件中没有重名事件,则可以正常传递给父组件。
扩展:
一般的vuejs项目开发当中,都是直接使用第三方框架,比如element-ui,iView等组件库。如果第三方组件不满足业务操作要求,则可以再多封装一层,比如动态设置下拉框的内容。使用$attrs和$listeners的时候,第三方的配置仍然可以从二次封装的组件中传递进去。并且在最外面能监听到第三方的组件事件。