Vue组件间的通信

301 阅读3分钟

1. 父子组件间通信

父传子 - props

props 基本用法

  • props的值有两种方式
    • 1 、 字符串数组。数组中的字符就是传递是的名称
    • 2 、 对象。对象可以设置传递是的类型,也可以设置默认值等

当需要对props进行类型等验证时,需要对象写法。支持一下写法

props: {
    // 基础类型检查('null'匹配任何类型)
    propA: Number,
    // 多个可能类型
    propB: [String,Number]
    // 必传字符串
    propC: {
      type: String,
      required: true
    },
    // 带默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 数组或对象的默认值必须从一个工厂函数获取
    propE: {
      type: Object,
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        return value > 10
      }
    }
}

父组件中,向子组件中传入数据
<parent>
    <child :cMovices="cMovices"></child>
</parent>
/* *************************************** */
子组件中,定义传入的变量
{
    props: ['cMovices'],
    data() {
        return {}
    }
}

注意 props: {}里的变量名称需要驼峰标识。 组件标签里不能使用驼峰标识,需要加横杠

子传父 - $emit
 // 子组件
<template>
    <div class="app">
       <input @click="sendMsg" type="button" value="给父组件传递值">
    </div>
</template>
<script>
export default {
 
    data () {
        return {
            //将msg传递给父组件
            msg: "我是子组件的msg",
        }
    },
     methods:{
         sendMsg(){
             //func: 是父组件指定的传数据绑定的函数,this.msg:子组件给父组件传递的数据
             this.$emit('func',this.msg)
         }
     }
}
/  子组件通过this.$emit()的方式将值传递给父组件

/ 注意:这里的func是父组件中绑定的函数名
</script>
// 父组件
<template>
    <div class="app">
        <child @func="getMsgFormSon"></child>
    </div>
</template>
<script>
import child from './child.vue'
export default {
    data () {
        return {
            msgFormSon: "this is msg"
        }
    },
    components:{
        child,
    },
    methods:{
            getMsgFormSon(data){
                this.msgFormSon = data
                console.log(this.msgFormSon)
            }
    }
}
</script>

2. 多层父子嵌套(隔代)组件间通信

provide/inject

通过provide/inject 实现在父组件中通过provide提供变量,然后在子组件中通过inject注入变量

优点: 无论子组件嵌套有多深,只要调用inject那么就可以从父组件中通过provide拿取数据

例如有A、B、C三个组件

A嵌套B,B嵌套C

// A 组件
<script>
  import comB from '../components/test/comB.vue'
  export default {
    name: "A",
    provide: {
      data: "AAA"
    },
    components:{
      comB
    }
  }
</script>

<script>
  import comC from '../components/test/comC.vue'
  export default {
    name: "B",
    inject: ['for'],
    data() {
      return {
        data: this.for
      }
    },
    components: {
      comC
    }
  }
</script>

// C.vue
<template>
  <div>
    {{demo}}
  </div>
</template>

<script>
  export default {
    name: "C",
    inject: ['for'],
    data() {
      return {
        data: this.for
      }
    }
  }
</script>
attrs/attrs/listeners

$attrs

包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (classstyle 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (classstyle 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

简单的理解就是:子组件可以通过this.$attrs接收父组件传递的除了props声明的所有属性

子组件中可以通过v-bind:$attrs,将属性继续的传递下去,孙组件可以访问到父组件的属性

// 父组件
<child name="zhangsan" age="12" height="188" />
// 子组件
<template>
    <grandson :$attrs height="180"/>
</template>
....
 export default {
    props: ['name'],
    ....
    created() {
        console.log(this.$attrs) // {age="12" height="188"}
    }
  }
// 孙组件
 export default {
    props: ['name'],
    ....
    created() {
        console.log(this.$attrs) // {age="12" height="180"}
    }
  }

注意

绑定的属性和 $attrs 中的属性有重复时,绑定的属性优先级会更高。

$listeners

包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

简单理解:组件可以通过this.$listeners接收父组件传递的除了props声明的所有事件

子组件中可以通过v-on:listeners,将属性继续的传递下去,放孙组件可以访问到父组件的事件,并且使用listeners,将属性继续的传递下去,放孙组件可以访问到父组件的事件,并且使用emit触发

// 父组件
<child @click='handleclick' @click.native='handleclickNative'/>
// 子组件
<template>
    <grandson v-on='$listeners' height="180"/>
</template>
....
 export default {
    created() {
        console.log(this.$listeners) // { handleclick: fn }
    }
  }
// 孙组件
 export default {
    ....
    created() {
       this.$emit('handleclick', 666)
    }
  }

注意:

绑定的事件和 $listeners 中的事件有重复时,并不会被覆盖掉,而是会类似于冒泡的方式被触发

当组件之间出现隔代关系时,它们之间的通信方式引入attrsattrs和listeners,

3. ref属性实现通信

ref属性有多种用法

1. 获取DOM元素

ref 加在普通的元素上,用this.ref.name 获取到的是Dom元素

···
<h1 ref='h1'>2222</h1>
···
mounted() {
    showDOM() {
        console.log(this.$refs.h1)
    }
}

2. 获取组件实例(实现组件间通信)

ref 加在子组件上,用this.$refs. 获取到的是组件实例,可以使用组件的所有方法并且获取组件中的数据

// 子组件
export default {
  data () {
    return {
      name: 'data-ref'
    }
  },
  methods: {
    sayRef () {
      console.log('ref')
    }
  }
}

// 父组件
<template>
  <child ref="A"></child>
</template>
<script>
  export default {
    mounted () {
      const A = this.$refs.A;
      console.log(A.name);  // data-ref
      comA.sayRef();  // ref
    }
  }
</script>

注意:

ref 需要在DOM渲染完成后才会有,在使用的时候确保DOM已经渲染完成。比如在生命周期 mounted(){} 钩子中调用,或者在 this.$nextTick(()=>{}) 中调用

4. 全局事件总线bus - 任意组件间通信

全局事件可以实现任意组件间通信

1、其本质是定义一个对象,让所有的组件对象都能看到它

2、 并且可以使用onon和emit去绑定和触发事件

3、 在接收数据的组件中,获取总线绑定事件

4、在发送数据组件中,获取总线触发的事件

定义事件总线

1—在main.js中设置全局事件总线

  • 在main.js中创建一个Vue实例化对象,因为只有在组件对象中或Vue实例化对象才能调用onon和emit
  • 要想实现所有组件对象都能看见总线对象,所以得把总线对象放在Vue原型上
  • 这个事件总线对象必须能调用onon和emit方法(总线对象必须是Vue的实例化对象或者是组件对象)
  • 在beforeCreate钩子放置$bus,在beforeCreate钩子中的this指向的是new Vue()这是实例化对象;在模板未解析前放置好后,在组件生效是就不会报错了
new Vue ({
    beforeCreate() {
        Vue.prototype.$bus = this
    },
    el: '#app',
    render: h => h(App)
})

2—在接收数据组件中使用

mounthed() {
   this.$bus.$on('propsBus', (data) => {
    	console.log('接收到了数据:', data)
	}) 
}
// 在组件销毁之前,最好进行解绑操作,因为所有的组件都在用$bus,组件被销毁后,还占着事件,这样不太好
beforeDestroy(){
    this.$bus.$off('propsBus')
}

3—在发送数据组件中使用

methods: {
    this.$bus.$emit('propsBus', 7777)
}

5. 消息订阅与发布 - 任意组件间通信

1— 安装pubsub库

npm i pubsub-js

2—引入

import pubsub from 'pubsub-js'

3— 订阅消息

接收数据: 在需要接收数据的组件中订阅消息,订阅回调函数留在组件自身

 methods(){
    demo(_,data){......}
  }
  mounted() {
    this.pubId = pubsub.subscribe('msgName',this.demo) //订阅消息执行demo回调函数
  }

注意:回调函数中的第一个参数固定是消息名称,是固定写法,第二个参数才是传过来的数据,基本上这个 函数的回调方法的第一个参数msgName是没用到的,所以可以使用英文下划线代替 _

this.pubId = pubsub.subscribe('msgName',(_,data)=>{})

最好在beforeDestroy钩子中,用PubSub.unsubscribe(pubId)去取消订阅。

  beforeDestroy() {
	 pubsub.unsubscribe(this.pubId) //接收的是一个订阅消息的Id,不是消息名称
       }

4—发布消息

发送数据

pubsub.publish('msgName',data)

部分笔记来源于codewhy老师