Vue(二):组件通讯

75 阅读2分钟

子组件调用父组件方法

1、直接在子组件中通过 this.$parent.event 来调用父组件的方法

父组件:

<template>
  <p>
    <child></child>
  </p>
</template>
<script>
  import child from '~/components/dam/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 '~/components/dam/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 '~/components/dam/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();

示例

简单示例一:

<!--ComponentA.vue-->
<script>
import bus from 'bus.js'  
export default {  
  mounted() {  
    // 监听事件
    bus.$on('custom-event', this.handleEvent)  
  },  
  methods: {  
    handleEvent(data) {  
      console.log(data)  
    }  
  }  
}
</script>

<!--ComponentB.vue-->
<template>  
  <button @click="handleClick">触发事件</button>  
</template>  

<script>
import bus from 'bus.js'  
export default {
  data() {
    return {
        str: '我来自 B 组件'
    }
  }
  methods: {
    handleClick() {
      // 触发事件
      bus.$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>  
import { EventBus } from './event-bus.js';  
  
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[]

主要用作孙组件调用爷组件方法。

爷/父:

<script>
export default {
  name: 'parent',
  components: { Child },
  provide() {
    return {
      parentEvent1: this.myEvent1,
      parentEvent2: this.myEvent2,
    };
  },
  methods: {
    myEvent1(params1, params2) {
      console.log(params1, params2)
    }
    myEvent2() {
      console.log(2)
    }
  }
};
</script>

子/孙:

<template>
  <el-button @click="handleClick">测试</el-button>
</template>
<script>

export default {
  name: 'child',
  injectL: ["parentEvent1", "parentEvent2"],
  methods: {
    handleClick() {
      this.parentEvent1('参数1', '参数2');
      this.parentEvent2()
    }
  }
};
</script>

$attrs

使用场景:

  • $attrs ['ətrz] 是在 Vue2.40 版本以上添加的。

  • 主要用作爷组件向孙组件传递数据。

作用:

包含了父作用域中没有被 prop 接收的所有属性(不包含 class 和 style 属性)。可以通过 v-bind="$attrs" 直接将这些属性传入内部组件。

爷:

<!-- A 组件 -->
<template>
  <div>
    <div class="a">
      <p>A组件</p>
      <p>A: {{params1}}</p>
      <p>A: {{params2}}</p>
      <el-button @click="handleClick">传递</el-button>

      <B :params1="params1" :params2="params2"></B>
    </div>
  </div>
</template>

<script>
import B from './B.vue';

export default {
  name: 'A',
  components: { B },
  data() {
    return {
      params1: '111',
      params2: '222'
    };
  },
  mounted() {
    console.log("A $attrs:", this.$attrs); // {} 空的
  },
  methods: {
    handleClick() {
      this.params1 = "params1"
      this.params2 = "params2"
    }
  }
};
</script>

<style lang="less" scoped>
.a {
  border: 1px solid red;
}
</style>

父:

<!-- B 组件 -->
<template>
  <div class="b">
    <p>B组件</p>
    <p>B: {{ $attrs.params1 }}</p>
    <p>B: {{ $attrs.params2 }}</p>
    <C v-bind="$attrs"></C>
  </div>
</template>

<script>
import C from "./C.vue";
export default {
  components: { C },
  name: "B",
  inheritAttrs: true, // 默认值是 true
// 如果 B 组件使用 props 接收了数据,那么 C 组件的 $attrs 就接收不到了
//   props: {
//     params1: {
//       type: String,
//       dedault: "",
//     },
//     params2: {
//       type: String,
//       dedault: "",
//     },
//   },
  mounted() {
    console.log("B $attrs:", this.$attrs); // { "params1": "111", "params2": "111" }
    // console.log("b params1:", this.params1);
    // console.log("b params2:", this.params2);
  },
};
</script>

<style lang="less" scoped>
.b {
  border: 1px solid #000;
}
</style>

子:

<!-- C组件 -->
<template>
  <div class="c">
    <p>C组件</p>
    <p>C: {{ params1 }}</p>
    <p>C: {{ params2 }}</p>
  </div>
</template>

<script>
export default {
  name: "C",
  inheritAttrs: true,
  // C 组件用 props 接收 A 组件传递的值
  props: {
    params1: {
      type: String,
      dedault: "",
    },
    params2: {
      type: String,
      dedault: "",
    },
  },
  mounted() {
    /* 
    如果 C 组件用 props 接受收了值,则 this.$attrs 是空的
    不用 props 接收值,则可以直接使用 $attrs.xxx 接收
    */
    console.log("C $attrs:", this.$attrs); // {} 空的
    console.log("C params1:", this.params1); // params1: 111
    console.log("C params2:", this.params2); // params2: 222
  },
};
</script>

<style lang="less" scoped>
.c {
  border: 1px solid blue;
}
</style>

页面:

在这里插入图片描述

执行顺序:

在这里插入图片描述

inheritAttrs: true 的作用:

在这里插入图片描述

官方解释:如果使用 $arrts 给子组件传递的数据,子组件不使用 props 接收,那么这些数据将作为子组件的特性,这些特性绑定在组件的 HTML 根元素上,在 vue2.40 版本之后,可以通过 inheritAttrs = false(默认是 true) 来控制这些特性是否显示在 DOM 元素上

作用:inheritAttrstrue:继承除props之外的所有属性;inheritAttrsfalse:只继承class属性

$listeners

作用:

包含所有父组件中的 v-on 事件监听器 (不包含.native修饰器的) ,可以通过v-on="$listeners"传入内部组件。

$attrs 类似,$attrs 是传递属性,$listeners 是传递方法。

例如:

<!--父-->
<template>
  <div class="outer">
    <h3>父组件</h3>
    <div>myData:{{ myData }}</div>
    <child @changeData="changeMyData"></child>
  </div>
</template>
<script>
import Child from "./Child";
export default {
  name: 'Parent',
  components: {Child},
  data() {
    return {
      myData: 100
    };
  },
  methods: {
    changeMyData(val) {
      this.myData = val;
    }
  }
}
</script>

<!--子-->
<template>
  <div class="outer">
    <h3>子组件</h3>
    <grand-child v-on="$listeners"></grand-child>
  </div>
</template>
<script>
import GrandChild from "./GrandChild";
export default {
  components: {GrandChild}
}
</script>

<!--孙-->
<template>
  <div class="outer">
    <h3>孙组件</h3>
    <input v-model="data1" @input="edit"/>
  </div>
</template>
<script>
export default {
  name: "GrandChild",
  data() {
    return {
      data1: 200,
    }
  },
  methods: {
    edit() {
      // 发送事件
      this.$emit("changeData", this.data1);
    }
  }
}
</script>