Vue 系列之:组件通讯
子组件调用父组件方法
1、直接在子组件中通过 this.$parent.event 来调用父组件的方法
父组件:
<template>
<p>
<child></child>
</p>
</template>
<script>
import child from './child';
export default {
components: {
child
},
methods: {
fatherMethod() {
console.log('测试');
}
}
};
</script>
子组件:
<template>
<p>
<button @click="childMethod()">点击</button>
</p>
</template>
<script>
export default {
methods: {
childMethod() {
this.$parent.fatherMethod();
}
}
};
</script>
2、父组件使用 v-on 监听事件,子组件使用 $emit 件触发事件
@
是 v-on 的缩写
父组件:
<template>
<p>
<child @method1="fatherMethod"></child>
</p>
</template>
<script>
import child from './child';
export default {
components: {
child
},
methods: {
fatherMethod(params) {
console.log('测试', params);
}
}
};
</script>
子组件:
<template>
<p>
<button @click="childMethod()">点击</button>
</p>
</template>
<script>
export default {
methods: {
childMethod() {
this.$emit('method1', params); // params 为参数,可不传
// this.$emit('method1');
}
}
};
</script>
3、父组使用 v-bind 绑定事件,子组件用 props 接收事件
:
是 v-bind 的缩写
父组件:
<template>
<p>
<child :method1="fatherMethod"></child>
</p>
</template>
<script>
import child from './child';
export default {
components: {
child
},
methods: {
fatherMethod() {
console.log('测试');
}
}
};
</script>
子组件:
<template>
<p>
<button @click="childMethod()">点击</button>
</p>
</template>
<script>
export default {
props: {
method1: {
type: Function,
default: null
}
},
methods: {
childMethod() {
if (this.method1) {
this.method1();
}
}
}
};
</script>
父组件调用子组件方法
1、通过 ref 直接调用子组件的方法
父组件:
<template>
<div>
<Button @click="fatherMethod">点击调用子组件方法</Button>
<Child ref="child"/>
</div>
</template>
<script>
import Child from './child';
export default {
methods: {
fatherMethod() {
this.$refs.child.childMethod();
},
},
}
</script>
子组件:
<template>
<div>我是子组件</div>
</template>
<script>
export default {
methods: {
childMethod() {
console.log('我是子组件的方法');
},
},
};
</script>
2、通过组件的$emit
、$on
方法(可以,但是没必要)
父组件:
<template>
<div>
<Button @click="fatherMethod">点击调用子组件方法</Button>
<Child ref="child"/>
</div>
</template>
<script>
import Child from './child';
export default {
methods: {
fatherMethod() {
this.$refs.child.$emit("getChildMethod") //子组件$on中的名字
},
},
}
</script>
子组件:
<template>
<div>我是子组件</div>
</template>
<script>
export default {
mounted() {
this.$nextTick(function() {
this.$on('getChildMethod', this.childMethod);
});
},
methods: {
childMethod() {
console.log('我是子组件方法');
}
}
};
</script>
兄弟组件
-
方法1:通过父组件作为中转
-
通过 ref 和 $parent
-
通过 provide 和 inject
-
-
方法2:使用 EventBus 事件总线
-
方法3:vuex,下一篇内容会讲
EventBus 使用方式
1、初始化——全局定义
可以将 eventBus
绑定到 vue
实例的原型上,也可以直接绑定到 window
对象上
//main.js
//注册方式一
Vue.prototype.$EventBus = new Vue();
//注册方式二
window.EventBus = new Vue();
2、监听事件
//使用方式一
this.$EventBus.$on('eventName', (param1, param2, ...) => {
//需要执行的代码
})
//使用方式二
EventBus.$on('eventName', (param1, param2, ...) => {
//需要执行的代码
})
3、触发事件
//使用方式一
this.$EventBus.$emit('eventName', param1, param2,...)
//使用方式二
EventBus.$emit('eventName', param1, param2,...)
4、移除监听事件
为了避免在监听时,事件被反复触发,通常需要在页面销毁时移除事件监听。或者在开发过程中,由于热更新,事件可能会被多次绑定监听,这时也需要移除事件监听。
//使用方式一
this.$EventBus.$off('eventName');
//使用方式二
EventBus.$off('eventName');
//移除所有
EventBus.$off();
5、示例
简单示例一:
<!--组件 A.vue-->
<script>
export default {
mounted() {
// 监听事件
this.$EventBus.$on('custom-event', this.handleEvent)
},
methods: {
handleEvent(data) {
console.log(data)
}
}
}
</script>
<!--组件 B.vue-->
<template>
<button @click="handleClick">触发事件</button>
</template>
<script>
export default {
data() {
return {
str: '我来自 B 组件'
}
}
methods: {
handleClick() {
// 触发事件
this.$EventBus.$emit('custom-event', this.str)
}
}
}
</script>
示例二:
假设兄弟组件有三个,分别是 A、B、C 组件,A 组件如何获取 B 或者 C 组件的数据
这时候就可以使用 EventBus。EventBus 是一种发布/订阅模式,用于在组件之间传递事件和数据。A 组件可以监听由 B 或 C 组件发布的事件,并在事件处理函数中获取传递的数据。
思路:
A 组件中使用 Event.$on
监听事件
B、C 组件中使用 Event.$emit
触发事件
// A.vue
<template>
<div>A 接收到的数据: {{ receivedData }}</div>
</template>
<script>
export default {
data() {
return {
receivedData: null
};
},
mounted() {
// 监听事件
EventBus.$on('custom-event', (data) => {
this.receivedData = data.message;
});
},
beforeDestroy() {
// 组件销毁前,移除事件监听器
EventBus.$off('custom-event');
}
};
</script>
// B.vue 和 C.vue
<template>
<button @click="sendData">发送数据</button>
</template>
<script>
export default {
methods: {
sendData() {
const data = { message: 'I am from B' };
// 触发事件
EventBus.$emit('data-from-a', data);
}
}
};
</script>
多层组件(爷孙)
provide() 和 inject[]
用于将数据或方法暴露给组件树中的任何后代组件,哪怕是深层次的后代组件都可以访问到这些数据,而无需通过 props 层层传递。
注意:provide 和 inject 主要用于单向数据传递,即从祖先组件流向后代组件。虽然可以在后代组件中修改注入的数据,但这种做法会破坏单向数据流的原则,导致数据流向不清晰,难以调试,因此不建议这样做。
Vue2 用法:
<!--爷/父 组件-->
<template>
<div id="app">
<Children></Children>
</div>
</template>
<script>
import Children from "./Children.vue";
export default {
name: 'parent',
components: { Children },
provide() {
return {
parentEvent: this.myEvent,
parentData: this.message,
parentStr: '字符串数据'
};
},
data() {
return {
message: 'data中的数据'
}
},
methods: {
myEvent(params1, params2) {
console.log(params1, params2)
},
}
};
</script>
<!--子/孙 组件-->
<template>
<el-button @click="handleClick">测试</el-button>
</template>
<script>
export default {
name: 'child',
inject: ["parentEvent", "parentData", "parentStr"],
methods: {
handleClick() {
this.parentEvent('参数1', '参数2');
console.log(this.parentData)
console.log(this.parentStr)
}
}
};
</script>
从上到下依次打印:
参数1 参数2
data中的数据
字符串数据
Vue3 用法:
<!--爷/父 组件-->
<template>
<div id="app">
<Children></Children>
</div>
</template>
<script setup>
import { ref, provide } from "vue";
import Children from "./Children.vue";
const message = ref('data中的数据');
const str = '字符串数据'
function myEvent(params1, params2) {
console.log(params1, params2)
}
provide('parentEvent', myEvent);
provide('parentData', message);
provide('parentStr', str);
</script>
<!--子/孙 组件-->
<template>
<el-button @click="handleClick">测试</el-button>
</template>
<script setup>
import { inject } from "vue";
const parentEvent = inject('parentEvent');
const parentData = inject('parentData');
const parentStr = inject('parentStr', '默认值');
function handleClick() {
parentEvent('参数1', '参数2')
console.log(parentData.value)
console.log(parentStr)
}
</script>
细心的朋友已经发现:在 Vue3 中,子组件打印的是 parentData.value,这说明 parentData 是一个响应式对象。
直接总结:
特性 | Vue2 | Vue3 |
---|---|---|
响应式支持 | provide 提供的数据不是响应式的 | provide 提供的数据是响应式的 |
默认值支持 | 不支持默认值 | 支持默认值,inject 的第二个参数就是默认值 |
$attrs 和 $listeners
$attrs
$attrs 是一个对象,包含了父组件传递给子组件的所有非 prop 属性(即没有在 props 中定义的属性)。
当你希望将父组件传递的属性传递给子组件的子组件时,可以使用 $attrs。
父组件:
<!-- 父组件 -->
<template>
<div id="app">
<Children :params1="params1" :params2="params2" />
</div>
</template>
<script>
import Children from "./Children.vue";
export default {
name: 'parent',
components: { Children },
data() {
return {
params1: '测试1',
params2: '测试2',
params3: '测试3',
}
},
mounted() {
setTimeout(() => {
this.params2 += 'timeout'
}, 5000);
},
};
</script>
子组件:
<!-- 子组件 -->
<template>
<div>
<p>params1: {{ $attrs.params1 }}</p>
<p>params2: {{ $attrs.params2 }}</p>
<p>params3: {{ $attrs.params3 }}</p>
<Groundson v-bind="$attrs" :params4="params4"/>
</div>
</template>
<script>
import Groundson from "./Groundson.vue";
export default {
name: 'children',
components: { Groundson },
props: {
params1: {
type: String,
default: ""
}
},
data() {
return {
params4: '测试4'
}
},
mounted() {
console.log("children $attrs:", this.$attrs);
},
};
</script>
孙组件:
<!-- 孙组件 -->
<template>
<div>
</div>
</template>
<script>
export default {
name: 'groundson',
mounted() {
console.log("groundson $attrs:", this.$attrs);
},
};
</script>
页面:
打印:
总结:
-
没有通过 v-bind 传递给子组件的,子组件的 $attrs 中不会有该属性
-
通过 v-bind 传递给了子组件,但是子组件使用了 props 接收的,子组件的 $attrs 中不会有该属性
-
$attrs 中的属性值是响应式的
-
在子组件中使用 v-bind=“$attrs” 可以将子组件的 $attrs 中的所有属性都传递给孙子组件,孙子组件也是按同样的规则接收
inheritAttrs 的作用:
观察页面元素发现:
子组件的根元素和孙子组件的根元素都多了一些属性
官方解释:默认情况下,父组件传递的,但没有被子组件解析为 props 的 attributes 绑定会被“透传”。这意味着当我们有一个单根节点的子组件时,这些绑定会被作为一个常规的 HTML attribute 应用在子组件的根节点元素上。我们可以通过设置 inheritAttrs 为 false 来禁用这个默认行为。
例如在子组件中加上 inheritAttrs: false
:
子组件根节点的属性消失了,由于没有在孙子组件中设置,孙子组件的根节点还保留着属性
$listeners
$listeners 包含了父组件传递给子组件的所有事件监听器(即 v-on 绑定的事件)。
与 $attrs 类似, $attrs 是传递属性, $listeners 是传递方法。这里就不再举例了。
注意:在 Vue3 中,$listeners 已经被移除,其功能被合并到了 $attrs 中。