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 中。