Vue2组件间通讯

Posted luckest

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue2组件间通讯相关的知识,希望对你有一定的参考价值。

Vue2组件间通讯

1、组件之间的关系

1.1、父子组件

假如在App组件中,引用了Student组件,那么我们就把App和Student两个组件的关系定义为父子,App是父组件,而被引用的Student是子组件。

1.2、兄弟组件

如果在App组件中,不仅引用了Student组件,又引用了School组件,那么School组件也是App的子组件。

既然School和Student都是App的子组件,那么School和Student就是兄弟组件。

1.3、爷孙组件

假如在Student组件中,引用了一个List组件,用于循环遍历学生的信息列表,那么List是Student的子组件。

而Student又是App的子组件,儿子的儿子是什么?坐过超市门口摇摇车的同学都知道,App是List的爷爷,List是App的孙子,那么App和List组件关系就是爷孙。

2、父子组件之间的通讯

什么是父子组件之间的通讯?就是传值,假如父组件有一个值要传给子组件,或者子组件有一个值要传给父组件,该怎么传?

2.1、子传父(绑定自定义事件)

第一种方式:自定义事件(使用@或v-on)

在父组件App.vue中定义一个自定义事件school,比如说:

<template>
  <School @school="getSchoolName" />
</template>

<script>
  import School from \'./components/School\';
  
  export default 
    name: \'App\',
    components: School,
    methods: 
      getSchoolName(name) 
        alert(\'我的子组件School给我传了一个学校名:\' + name);
      
    
  
</script>

在子组件School.vue中去触发父组件定义的school事件:

<template>
    <div>
        <h2>学校名: name </h2>
        <button @click="sendSchoolName">给父组件传学校名</button>
    </div>
</template>

<script>
  export default 
      name: \'School\',
      data() 
          return 
              name: \'test\',
          
      ,
      methods: 
          sendSchoolName() 
              // $emit触发School组件上的school事件
              this.$emit(\'school\', this.name);
          
      
  
</script>

第二种方式 自定义事件(使用ref)

父组件App.vue中,在生命周期钩子mouted中:

<template>
  <School ref="schoolComponent" />
</template>

<script>
  import School from \'./components/School\';
  
  export default 
    name: \'App\',
    components: School,
    methods: 
      getSchoolName(name) 
        alert(\'我的子组件School给我传了一个学校名:\' + name);
      
    ,
    mounted() 
      // 自定义事件 
      this.$refs.schoolComponent.$on(\'school\', this.getSchoolName);
      // 比使用@或v-on的方式更加灵活,可以加条件判断,又或者使用$once自定义事件可以只触发一次
      // this.$refs.schoolComponent.$once(\'school\', this.getSchoolName);
    
  
</script>

School.vue中的代码和上述一致。

2.2、子传父(解绑自定义事件)

  • 解绑一个自定义事件

    • this.$off(\'school\');
      
  • 解绑多个自定义事件

    • this.$off([\'school\', \'test\']);
      
  • 解绑所有自定义事件

    • this.$off();
      

代码示例:

<template>
    <div>
        <h2>学校名: name </h2>
        <button @click="sendSchoolName">显示名字</button>
        <button @click="unbind">解绑自定义事件</button>
        <button @click="destroy">销毁组件实例</button>
    </div>
</template>

<script>
export default 
    name: \'School\',
    data() 
        return 
            name: \'test\',
        
    ,
    methods: 
        sendSchoolName() 
            // 触发School组件上的school事件
            this.$emit(\'school\', this.name);
            // 触发School组件上的demo事件
            this.$emit(\'demo\');
        ,
        unbind() 
            // this.$off(\'school\'); // 解绑一个自定义事件
            // this.$off([\'school\', \'demo\']); // 解绑多个自定义事件
            this.$off(); // 解绑所有的自定义事件
        ,
        destroy() 
            this.$destroy(); // 销毁了当前组件的实例,销毁之后所有的School实例的自定义事件就全部不奏效了
        
    

</script>

2.3、父传子(props)

通过props配置项,可以接受到外部过来的数据,通常用于父组件往子组件传递值。

  • 传递数据

    • <Demo name="xxx" />
      
  • 接收数据

    • // 第一种:只接收
      props: [\'name\'],
      // 第二种:限制类型
      props: 
        name: String
      ,
      // 第三种:限制类型、必要性、指定默认值
      props: 
        name: 
          type: String, // 类型
          required: true, // 必要性
          // default: \'张三\', // 默认值,和required不可以同时使用,因为默认值是没有传值的时候默认值,都required了那肯定有值传过来
        
      
      

注意:props中的值是只读的,无法直接修改,否则vue会发出警告。如果要修改props中传过来的值,那也要先存到data中再做修改。

代码示例:

父组件App.vue:

<template>
  <div>
    <Student name="jack" :age="18" sex="男" />
  </div>
</template>

<script>
import Student from \'./components/Student.vue\'

export default 
  name: \'App\',
  components: 
    Student
  

</script>

子组件Student.vue:

<template>
    <div>
        <h2>学生姓名: name </h2>
        <h2>学生年龄: age </h2>
        <h2>学生性别: sex </h2>
        <!-- 可以修改props的值,但是控制台会报错,会导致Vue出现一些问题 -->
        <button @click="updateAge">尝试修改收到的年龄</button>
    </div>
</template>

<script>
export default 
    name: \'Student\',
    data() 
        return 
            // 如果想修改props的值,得先放在data中,就不会报错
            myAge: this.age
        
    ,
    methods: 
        updateAge() 
          this.myAge = 100
        
    ,
    // props: [\'name\', \'age\', \'sex\'] // 简单接收(开发中这个写得多)

    // 接收的同时对数据进行类型限制
    // props: 
    //     name: String,
    //     age: Number,
    //     sex: String,
    // 

    // 接收的同时对数据进行类型限制 + 默认值的指定 + 必要值的限制
    props: 
        name: 
            type: String,
            required: true, // 名字是必须的
        ,
        age: 
            type: Number,
            default: 99, // age可传可不传,如果没传,默认值是99
        ,
        sex: 
            type: String,
            required: true,
        
    

</script>

3、事件总线

如果有人看到这就会奇怪,怎么2中只讲了父子组件之间的通讯,那兄弟组件爷孙组件该怎么通讯呢?难道兄弟和爷孙之间代沟太大,无法交流吗?

并非如此,这里要介绍一种可以适用于任意组件之间通讯的技术,兄弟和爷孙就可以利用这个技术进行通讯,这个技术叫做事件总线

1、安装全局事件总线,在脚手架中main.js入口文件中进行全局安装$bus

import Vue from \'vue\'
import App from \'./App.vue\'

Vue.config.productionTip = false

// 创建Vue实例对象 --- vm
new Vue(
  render: h => h(App),
  beforeCreate() 
    Vue.prototype.$bus = this; // 安装全局事件总线
  ,
).$mount(\'#app\')

bus这个英文单词有公交车,也有总线的意思。顾名思义,公交车嘛,谁都可以上,都可以往上面存数据,取数据。

Vue.prototype.$bus = this,为什么这么写?

如果掌握了JS高级中的原型与原型链可以看看,否则可以跳过。

首先,Vue.prototype是Vue构造函数的原型对象,而上述代码中的new Vue(),new出来的是Vue的实例对象,通常我们命名为vm。(Vue是构造函数,vm是Vue构造函数的实例对象)

众所周知,构造函数Vue的实例对象vm,是可以通过隐式原型链_proto_,访问到Vue.prototype(原型对象)上的属性和方法的,这就确保了vm可以访问到$bus上的数据。

其次,vm下面管理着很多组件,这些组件并不是Vue构造函数new出来的,而是VueComponent构造函数new出来的,通常命名为vc,vc有无数个,而vm只有一个,一人之下,管理着所有组件。如果不使用脚手架,就可以看得很清楚,如下代码示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>组件化编程</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
</head>
<body>
    <div id="root">
        <school></school>
        <hr>
        <student></student>
    </div>
    <script>
        Vue.config.productionTip = false;

        // 创建school组件
        const school = Vue.extend(
            // el:\'#root\' // 组件定义时一定不要写el
            template: `
                <div>
                    <h2>学校名: schoolName </h2>
                    <h2>地址: address </h2>
                </div>
            `,
            data() 
                return 
                    schoolName: \'test\',
                    address: \'福建\'
                
            
        )

        // 创建student组件
        const student = Vue.extend(
            template: `
                <div>
                    <h2>学生名: studentName </h2>
                    <h2>学生年龄: age </h2>
                </div>
            `,
            data() 
                return 
                    studentName: \'jack\',
                    age: 18
                
            
        );

        // 创建vm
        new Vue(
            el: \'#root\',
            // 注册组件(局部注册)
            components: 
                school,
                student
            
        )
    </script>
</body>
</html>

上述代码可以看出,vm只有一个,是new Vue出来的,而vm所管理的vc有多个,是通过Vue.extend这个API生成的,其实这个API内部就是帮我们做了new VueComponent。而VueComponent有一个重要的内置关系,就是:

  • VueComponent.prototype._proto_===Vue.prototype
  • VueComponent构造函数的原型对象的隐式原型链__proto__是指向Vue.prototype的,这也是为什么不直接new VueComponent的原因,就是要让Vue.extend这个API帮忙做这一步。

因此,既然vc实例对象,一样可以通过隐式原型链__proto__去访问到Vue原型对象。

至此,vm和vc都共享Vue.prototype上面的属性和方法,这也就解释了Vue.prototype.$bus = this的前半句,为什么要把$bus放到Vue.prototype上,就是为了让所有的组件都可以访问到$bus,这辆公交车。

而this是什么呢?自然是vm。

为什么$bus要等于vm?为了调用vm上面的自定义事件API,没错,事件总线的本质依旧是自定义事件,代码示例如下:

兄弟组件School和Student之间进行通讯:

School.vue(在$bus上面自定义或者发布一个hello事件,来收数据):

<template>
    <div>
        <h2>学校名: name </h2>
    </div>
</template>

<script>
export default 
    name: \'School\',
    data() 
        return 
            name: \'test\',
        
    ,
    mounted() 
        this.$bus.$on(\'hello\', (data) => 
            console.log(\'我是School组件,我收到了数据\', data);
        );
    ,
    beforeDestroy() 
        // 如果School组件被销毁,就同时解绑$bus上面注册或发布的自定义事件,这一步很有必要
        this.$bus.$off(\'hello\');
    

</script>

Student.vue(触发$bus上面的hello事件,来传数据):

<template>
    <div>
        <h2>学生姓名: name </h2>
        <h2>学生性别: sex </h2>
        <button @click="sendStudentName">把学生名字传给School组件</button>
    </div>
</template>

<script>
export default 
    name: \'Student\',
    data() 
        return 
            name: \'张三\',
            sex: \'男\',
        
    ,
    methods: 
        sendStudentName() 
            this.$bus.$emit(\'hello\', this.name);
        
    

</script>

ok,以上就是兄弟组件通过事件总线进行数据通讯,爷孙组件甚至曾爷爷曾孙子都一样可以通过事件总线进行通讯。

可能会萌新会问一些无聊的问题,比如说School和Student为什么是兄弟,不是姐妹?亦或者它们之间谁是兄,谁是弟之类的无聊问题。

我的解答是,这只是一种好理解的说法,它们之间没有长幼关系,而姐妹,如果你喜欢也可以叫姐妹。

任意组件之间进行通讯,除了事件总线的方法,还可以通过安装一些第三方库来进来通讯,比如消息的订阅与发布库pubsub.js,有兴趣可以去了解一下,和事件总线很相似。

vue2.0非父子间进行通讯

在vue中,父组件向之组件通讯使用的是props,子组件向父组件通讯使用的是$emit+事件,那非父子间的通讯呢,在官方文档上只有寥寥数笔,

概念很模糊,这个空的vue实例应该放在哪里呢,光放文档并没有明确的描述,经过查证一些其他的资料,发现其实这个非父子间的通讯是这么用的:

首先,这个空的实例需要放到根组件下,所有的子组件都能调用,即放在main.js下面,如图所示:

import Vue from \'vue\'
import App from \'./App\'
import router from \'./router\'


Vue.config.productionTip = false;


/* eslint-disable no-new */
new Vue({
  el: \'#app\',
  router,
  data:{
    Hub:new Vue()
  },
  template: \'<App/>\',
  components: { App }
});

  我的两个组件分别叫做child1.vue,child2.vue,我现在想点击child1.vue里面的按钮来改变child2.vue里面的数值,这个时候我们需要借助一个$root的工具来实现:

child1.vue:

<template lang="pug">
  div this is child
    span(@click="correspond") 点击进行非组件之间的通信
</template>
<script>
  export default{
    methods: {
      correspond(){
          this.$root.Hub.$emit("change","改变")
      }

    }
  }
</script>

  

child2.vue:

<template lang="pug">
  div this is child2
    span {{message}}
</template>
<script>
  export default{
    data(){
      return {
        message: "初始值"
      }
    },
    created(){
      this.$root.Hub.$on("change", () => {
        this.message = "改变"
      })
    }
  }
</script>

  

此时就已经可以达到我们想要的效果啦。

以上是关于Vue2组件间通讯的主要内容,如果未能解决你的问题,请参考以下文章

vue2 使用vuex进行组件间的通讯 element-ui Aside折叠与展开

vue2.0中eventBus实现兄弟组件通讯

vue2组件通信之$parent/$root

Vue2.0 实战项目 组件间通信的方法

Vue2.0组件间数据传递

Vue2.0父子组件间事件派发机制