# 组件间通信
- props 和 $emit 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过 $emit 触发事件来做到的
- $attrs 和 $listeners A->B->C。Vue 2.4 开始提供了$attrs和$listeners来解决这个问题
- $parent 和 $children 获取父子组件实例
- $refs 获取实例
- 父组件中通过 provider 来提供变量,然后在子组件中通过 inject 来注入变量
- eventBus 创建一个EventBus类负责事件派发、监听和回调管理
- Vuex 创建唯一的全局数据管理者store,通过它管理数据并通知组件状态变更
# props/$emit
//App.vue父组件
<template>
<div id="app">
<!-- 传递属性 -->
<users v-bind:users="users"></users>
</div>
</template>
<script>
import Users from "./components/Users"
export default {
name: 'App',
data(){
return{
users:["Henry","Bucky","Emily"]
}
},
components:{
"users":Users
}
}
//users子组件
<template>
<div class="hello">
<ul>
<!-- 遍历属性 -->
<li v-for="user in users">{{user}}</li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props:{
// 定义属性名及参数验证
users:{
type: Array,
required: true
}
}
}
</script>
# $attrs/$listeners
多级组件嵌套需要传递数据时,如果仅仅是传递数据,而不做中间处理,为此Vue2.4 版本提供了另一种方法---- $attrs/$listeners
$attrs:包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。
$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。
简单来说:$attrs 与 $listeners 是两个对象,$attrs 里存放的是父组件中绑定的非 Props 属性,$listeners 里存放的是父组件中绑定的非原生事件。
// child:并未在props中声明foo
<p>{{$attrs.foo}}</p>
// parent
<HelloWorld foo="foo"/>
# $parent / $children
- $parent:获取父组件实例,如果当前实例有的话。
- $children:当前实例的直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用 $children 来进行数据绑定,考虑使用一个数组配合 v-for 来生成子组件,并且使用 Array 作为真正的来源。
可以直接调用组件的方法或访问数据。
需要注意 $children 并不保证顺序,因为组件有可能是异步的。也不是响应式的。
子组件向上派发事件 触发指定父组件的自定义事件
/**
*
* @param {*} eventName 事件名
* @param {*} componentName 组件名
* @param {*} value 子组件向父组件传递的数据
* 子组件向上派发事件 触发指定父组件的自定义事件
*/
Vue.prototype.$dispatch = function (eventName,componentName, value){
let parentCom = this.$parent;
while (parentCom){
// 向上派发事件 触发指定父组件的自定义事件
if (parentCom.$options.name === componentName){
parentCom.emit(eventName, value); // 触发自定义事件 没有绑定不会触发
break;
}
parentCom = this.$parent;
}
}
父组件向下派发事件 触发指定子组件的自定义事件
/**
*
* @param {*} eventName 事件名
* @param {*} componentName 组件名
* @param {*} value 父组件向子组件传递的数据
* 父组件向下派发事件 触发指定子组件的自定义事件
*/
Vue.prototype.$broadcast = function (eventName, componentName, value) {
let children = this.$children;
function broadcast(children) {
for (let i = 0; i < children.length;i++){
let child = children[i];
if (child.$options.name === componentName){
child.emit(eventName, value); // 触发自定义事件 没有绑定不会触发
return;
}else{
child.$children && broadcast(child.$children); // 递归遍历子节点
}
}
}
broadcast(children)
}
# ref
被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例:
<!-- `vm.$refs.p` will be the DOM node -->
<p ref="p">hello</p>
<!-- `vm.$refs.child` will be the child component instance -->
<child-component ref="child"></child-component>
// parent
<HelloWorld ref="hw"/>
// parent组件直接修改 HelloWorld 子组件数据
mounted() {
this.$refs.hw.xx = 'xxx'
}
当 v-for 用于元素或组件的时候,引用信息将是包含 DOM 节点或组件实例的数组。
关于 ref 注册时间的重要说明:因为 ref 本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们 - 它们还不存在!$refs 也不是响应式的,因此你不应该试图用它在模板中做数据绑定。
# provide/inject
Vue2.2.0新增API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。
provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
会造成单向数据流混乱,不好追溯数据来源,通常用于工具库中。
// 父级组件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子组件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
# eventBus
事件总线:创建一个EventBus类负责事件派发、监听和回调管理
利用new 一个新的vue实例,可以轻松实现组件之间数据通信。
import Vue from 'vue'
const app= new Vue({
el:'#app',
router,
store
});
Vue.prototype.$eventBus = new Vue();
// Boy组件 发射dinner事件
<template>
<div>男孩
<button @click="sayToGirl()">对女孩说话</button>
</div>
</template>
<script>
export default {
methods: {
sayToGirl(){
this.$bus.$emit('dinner','你饿吗');
}
}
}
</script>
// Girl组件 监听dinner事件
<template>
<div>
女孩 <span>男孩对我说: {{message}}</span>
</div>
</template>
<script>
export default {
data(){
return {message:''}
},
mounted() {
this.$bus.$on('dinner',(data)=>{
this.message = data;
})
}
}
</script>
# 基于发布订阅实现 EventBus
实践中可以用Vue代替EventBus,因为它已经实现了相应功能
class EventBus {
constructor() {
this.callbacks = {};
}
$on(name, fn) {
this.callbacks[name] = this.callbacks[name] || [];
this.callbacks[name].push(fn);
}
$emit(name, args) {
if (this.callbacks[name]) {
// 存在 遍历所有callback
this.callbacks[name].forEach(cb => cb(args));
}
}
}
Vue 进阶 →