<p>从vue1.x过来的都知道,在vue2.0中,父子组件间事件通信的<code>$dispatch</code>和<code>$broadcase</code>被移除了。官方考虑是<strong>基于组件树结构的事件流方式实在是让人难以理解,并且在组件结构扩展的过程中会变得越来越脆落</strong>。特别是在组件层级比较深的情况下。通过广播和事件分发的机制,就显得比较混乱了。</p> <p><strong>官方在废除的同时,也为我们提供了替换方案,包括实例化一个空的vue实例,使用<a href="https://cn.vuejs.org/v2/guide/migration.html?#dispatch-" rel="nofollow noreferrer">$emit反应子组件上的状态变化</a></strong></p> <h2>1.使用$emit触发事件</h2> <p><code>helloWorld.vue</code>作为父组件,<code>dialogConfigVisible</code>变量控制子组件弹框显示或隐藏。<br><code>configBox.vue</code>作为子组件,假设为封装的公告弹窗。</p> <p>在父组件中 helloWorld.vue 中</p> <p><strong>< template/></strong></p>
<config-box
:visible="dialogConfigVisible"
@listenToConfig="changeConfigVisible"
> </config-box>
<p><strong>script</strong></p>
data(){
return {
dialogConfigVisible:true
}
}
methods: {
changeConfigVisible(flag) {
this.dialogConfigVisible = flag;
}
}
<p>然后,在子组件 configBox.vue 中,主要在任意事件回调中,使用 <code>$emit</code>来触发自定义的 <code>listenToConfig</code>事件,后面还可以加上参数传给父组件。比如,在子组件弹窗上点击×关闭时,通知父组件 <code>helloWorld.vue</code>我要关闭了,主要方便父组件改变相应状态变量,并传入false到自定义的事件中。</p> <p><strong>script</strong></p>
methods:{
dialogClose() {
this.show = false;
this.$emit("listenToConfig", false)
}
}
<p>在子組件中,主动触发<code>listenToConfig</code>事件,并传入参数 false, 告诉父组件 <code>helloWorld.vue</code>对话框要关闭了。这里就可以避免父组件中的状态未变化,再次刷新页面的时候对话框会自动出现。</p> <h2>2.实例化一个空的vue实例bus</h2> <p><strong>这里实例化一个bus 空vue实例,主要为了统一管理子组件和父组件相互通信,通过bus 作为媒介,</strong><br>首先新建一个bus.js 文件,在里面新建一个对象,父组件为<code>table.vue</code>, 子组件为<code>tableColumn.vue</code></p>
// bus.js
import Vue from "vue";
export var bus = new Vue({
data:{
scrollY:false
},
methods:{
updateScrollY(flag){
this.scrollY = flag;
}
}
})
<p>然后分别引入:</p>
// table.vue
<script>
import {bus} from "./bus"
export default {
created(){
bus.$on('getData',(argsData)=>{
// 这里获取子组件传来的参数
console.log(argsData);
})
}
}
</script>
// tableColumn.vue
<script>
import {bus} from "./bus"
export default{
methods(){
handleClick(){
bus.$emit('getData',{data:"from tableColumn!"})
}
}
}
</script>
<p>上面的父子组件中,父组件中利用<code>bus</code>注册监听事件<code>getData</code>,子组件中一旦有状态变化,就触发<code>bus</code>上对应的事件。</p> <p><strong>这种利用空实例的方式,相当于创建了一个事件中心,所以这种通信同样适用于非父子组件间的通信,</strong></p> <h2>3.多级父子组件通信</h2> <p><strong>有时,可能想要实现通信的两个组件不是直接的父子组件,而是祖父和孙子,或者是跨越了更多层级的父子组件</strong></p> <p>不可能由子组件一级一级的向上传递参数,来达到通信的目的,虽然现在我们理解的通信都是这样经过中转的。可以通过<code>while</code>等循环,不断向上遍历,直到找到目标父组件,就在对应的组件上触发事件。</p> <p>下面就只<code>element-ui</code>实现的一个<a href="https://github.com/ElemeFE/element/blob/dev/src/mixins/emitter.js" rel="nofollow noreferrer">父子组件通信的<code>mixins</code></a>,对于组件同步有很大的作用。在<a href="https://mp.weixin.qq.com/s/vD0E5YzBtk9w7ZeddtXy1Q" rel="nofollow noreferrer">element-ui 的优点概述</a>中也特意提到这个组件通信</p>
function broadcast(componentName, eventName, params) {
// 向下遍历每个子节点,触发相应的向下广播的 事件
this.$children.forEach(child => {
var name = child.$options.componentName;
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
export default {
methods: {
// 向上遍历父节点,来获取指定父节点,通过$emit 在相应的 组件中触发 eventName 事件
dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root;
var name = parent.$options.componentName;
// 上面的componentName 需要在每个vue 实例中额外配置自定义属性 componentName,
//可以简单替换成var name = parent.$options._componentTag;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.componentName;
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
},
broadcast(componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
}
};
<p><strong>首先定义两个嵌套的组件 f1.vue 和 c1.vue</strong>,实例是:</p>
<f1>
<c1></c1>
</f1>
<p>然后,分别定义两个父子组件:</p>
c2.vue
<template>
<section>
<button type="button" name="button" @click="dispatchTest">点击一下,就可以</button>
</section>
</template>
<script type="text/javascript">
import Emitter from "../mixins/emitter";
export default {
name: "c2",
mixins: [Emitter],
componentName:'c2',
methods: {
dispatchTest() {
this.dispatch('f1', 'listenerToC1', false);
}
}
}
</script>
f1.vue
<template type="html">
<div class="outBox-class">
<slot>
</slot>
</div>
</template>
<script type="text/javascript">
import Emitter from "../mixins/emitter";
export default {
name: "f1",
mixins: [Emitter],
componentName: 'f1',
mounted() {
this.$on("listenerToC1", (value) => {
alert(value);
})
}
}
</script>
<p>这样,就可以在子组件中点击按钮,触发 <code>listenerToC1</code>事件,在父组件中监听到这个事件,<br>其实更<code>$emit</code>触发事件类似。不同之处在于,这里可以多级嵌套,不一定是直接的父子组件都可以触发到。</p> <h2>4 .sync 修饰符</h2> <p>在Vue1.x中,利用prop进行"双向绑定",实现父子组件通信,都会用到<code>.sync</code>修饰符,可以将子组件中对应的prop值变化同步到父组件中。但是,这样就破坏了单向数据流,在2.0版本中被移除了,在2.3.0版本中又以一种语法糖的形式加了进来。<br>可以看下<a href="https://cn.vuejs.org/v2/guide/components.html#sync-" rel="nofollow noreferrer">文档上给出的实例</a></p>
<comp :foo.sync=“bar”></comp>
<p>被扩展为</p>
<comp :foo="bar" @update:foo= "val =>bar=val"></comp>
<p>其实跟本文中第一种方法基本一致,更加简化了。</p> <p>同样<code>helloWorld.vue</code>作为父组件, <code>configBox.vue</code>作为子组件,</p>
<config-box
:visible.sync="dialogConfigVisible"
> </config-box>
<p>然后在子组件中,显式的触发更新事件:</p>
methods:{
dialogClose() {
this.show = false;
this.$emit("update:visible", false)
}
}
<p>这样visible 的变化就能同步到父组件中了。</p>
原文地址:https://segmentfault.com/a/1190000012729649
来源:oschina
链接:https://my.oschina.net/u/4299463/blog/3751931