vue项目中,组件间的通信是最常用到的,也是面试必问的问题之一。
组件通信可以分为几种类型:
1、父子通信
1.1 父传子
1.2 子传父
2、跨级传递
2.1祖父传孙
3.1孙传祖父
3、同级组件间通信
首先说一下通用的方式,即不管哪种场景都在功能上可以实现,撇开具体场景的适合程度,其实也就是全局的通信方式。
一、vue bus 以vue实例为总线,传递数据
新建bus.js和busA.vue、busB.vue:
bus.js
import Vue from "vue"
const Bus = new Vue();
export default Bus;
在main.js引入,挂载到全局上去:
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import Bus from "@/utils/bus";
Vue.config.productionTip = false;
Vue.prototype.bus = Bus;
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
busA.vue:
<template>
<div class="input">
<button @click="handleClick">click</button>
</div>
</template>
<script>
export default {
data() {
return {
};
},
methods: {
handleClick() {
this.bus.$emit("change", 12333) // 触发事件
}
}
};
</script>
<style>
.input{
display: flex;
}
</style>
busB.vue:
<template>
<div class="input">
</div>
</template>
<script>
export default {
data() {
return {
};
},
created() {
this.bus.$on('change', this.handleClick) // 监听事件
},
methods: {
handleClick(val) {
console.log(val)
}
}
};
</script>
<style>
.input{
display: flex;
}
</style>
这样就实现了兄弟组件之间的数据传递,当然也适合所有类型的组件通信。
二、vuex 官方提供的状态管理器,统一管理状态,可参考官方文档。
接下来就是比较定制化的通信方式了:
一、父子通信
1、父传子:props
子传父:$emit
这两种方式都是最常用也是最简单的,可参考官方文档
2、通过实例通信:
$children
$parent
获取节点实例,然后访问实例下的数据或方法
3、父组件里给子组件设置ref属性,通过this.$refs.name访问子组件实例
二、隔代传递
利用组件的name属性作为标识,通过递归、遍历找到name对应的实例并返回,只要拿到了实例,那就可以随意访问实例下的数据和方法了。
1、向上查找最近指定节点
export const findComponentUpword = (context,componentName) => {
let parent = context.$parent;
let name = parent.$options.name;
while(parent && (!name || [componentName].indexOf(name) < 0) {
parent = parent.$parent;
if(parent) {
name = parent.$options.name;
}
}
return parent || null;
}
组件中调用(父组件一定要有具体的name属性才行,不然找不到):
import {findComponentUpward} from "@/utils"
、、、、
、、、、
let fatherComponent = findComponentUpward(this,"FatherName"); // 父组件名称
2、向上查找所有指定节点
export const findComponentsUpward = (context,componentName) => {
let parents = [];
let parent = context.$parent;
let name = parent.$options.name;
if(parent) {
if(name === componentName) {
parents.push(parent);
}
return parents.concat(findComponentsUpward(parent,componentName));
}else {
return [];
}
}
组件中调用:
import {findComponentsUpward} from "@/utils"
、、、、
、、、、
let fatherComponent = findComponentsUpward(this,"FatherName"); // 父组件名称, 返回匹配组件的实例数组
3、向下查找最近的指定节点
export const findComponentDownward = (context,componentName) => {
let children = context.$children;
let child = null;
if(children.length) {
for(const childItem in children) {
const name = childItem.$options.name;
if(name === componentName) {
child = childItem;
break;
}else {
child = findComponentDownward(childItem,componentName);
if(child) {break}
}
}
}
return child;
}
4、向下查找所有的指定节点
export const findComponentsDownwards = (context,componentName) => {
return context.$children.reduce((components, child) => {
if(child.$options.name === componentName) {
components.push(child);
}
const findChildren = findComponentsDownwards(child,componentName);
return [...components,...findChildren];
},[])
}
5、provide、inject方式
vue的api原生提供的方法,在父组件向下注入一个provide,不管子组件嵌套有多深,都可以通过inject获取到
父组件:
<template>
<div class="wrapper">
</div>
</template>
<script>
export default {
props: {
},
// provide() { // 如果父组件本身是一个组件
// return {
// father:this
// }
provide: {
father: this
}
},
data() {
return {
testData: "哈哈哈哈"
};
},
created() {
},
methods: {
}
};
</script>
子组件:
<template>
<div class="son">
</div>
</template>
<script>
export default {
inject: ['father'],
props: {
},
data() {
return {
};
},
mounted() {
console.log(this.father.testData) // 哈哈哈哈
},
methods: {}
};
</script>
<style lang="css">
</style>
有了这些组件通信方式,再复杂的业务需求也能得到满足了
========================================================
2079.10.6
父子组件的通信方式还有一种,就是同过props的Function类型传递:
父组件a.js
<template>
<div class="app-container">
<upload-excel-component :on-success="handleSuccess" :before-upload="beforeUpload" />
<el-table :data="tableData" border highlight-current-row>
<el-table-column v-for="item of tableHeader" :key="item" :prop="item" :label="item" />
</el-table>
</div>
</template>
<script>
import UploadExcelComponent from '@/components/UploadExcel/index.vue'
export default {
name: 'UploadExcel',
components: { UploadExcelComponent },
data() {
return {
tableData: [],
tableHeader: []
}
},
methods: {
beforeUpload(file) {
const isLt1M = file.size / 1024 / 1024 < 2
if (isLt1M) {
return true
}
this.$message({
message: '上传文件不得大于2M。',
type: 'warning'
})
return false
},
handleSuccess({ results, header }) {
this.tableData = results
this.tableHeader = header
}
}
}
</script>
传递两个函数给UploadExcel组件
UploadExcel/index.vue
<template>
<div>
<input ref="excel-upload-input" class="excel-upload-input" type="file" accept=".xlsx, .xls" @change="handleClick">
</div>
</template>
<script>
export default {
props: {
beforeUpload: Function,
onSuccess: Function
},
data() {
return {
loading: false,
excelData: {
header: null,
results: null
}
}
},
methods: {
generateData({ header, results }) {
this.excelData.header = header
this.excelData.results = results
this.onSuccess && this.onSuccess(this.excelData) // 子传父
},
handleClick(e) {
const files = e.target.files
const rawFile = files[0] // only use files[0]
if (!rawFile) return
this.upload(rawFile)
},
upload(rawFile) {
this.$refs['excel-upload-input'].value = null
const before = this.beforeUpload(rawFile) // 父传子
if (before) {
this.readerData(rawFile)
}
},
readerData(rawFile) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = e => {
、、、
this.generateData({ header, results })
resolve()
}
reader.readAsArrayBuffer(rawFile)
})
}
}
}
</script>
通过Function类型,父子组件就可以轻松传递值了