vue2中组件通讯的总结

309 阅读10分钟

vue2中组件通讯的总结

1.props(频率高)

props适用于父子之间传递数据。可以实现父传子(常用),也可以实现子传父。

父传子

  • 在父组件中给子组件实例上,添加标签属性,并将需要传递的数据写在属性值中。

    <!-- props数据传递,父传子,直接在子组件实例中传递 -->
    <Child1 :msg="msg"></Child1>
    
  • 子组件在自己的配置对象中,设置props接收数据。

      // 在子组件中要获取父组件传递的props数据,要先接收。
      // 第一种以数组的方式接收
      // props: ['msg'],
      // 第二种以对象的方式接收
      // props: {
      //   // 键为要接收的数据名,值是接收的数据的类型
      //   msg: String,
      // },
      // 以对象形式接收还有可以对数据在配置
      props: {
        msg: {
          type: String,
          // default:'ovo', // 默认值
          required: true, // 是否必须有值,与默认值是冲突的不会同时配置
        },
      },
    

子传父

  • 在父组件中给子组件实例上,添加标签属性,属性值是一个接收数据的函数

     <!-- props数据传递,子传父,在子组件实例中从传递一个函数,通过函数的参数来获取子组件传递的数据 -->
     <Child2 :fn="getCount"></Child2>
    
    // 使用props传递函数给子组件实现子传父,该函数一定要写在methods中!
    // 因为需要在函数中拿到子组件传的数据后对父组件中的数据进行操作,如果不定义在methods中函数内是无法操作父组件上的数据的
    methods: {
        // 当子组件调用该函数中,可以通过形参c接收到子组件传递的实参数据
        getCount(c) {
            console.log(c)
            // 拿到子组件的数据后可以对数据进行操作
            this.count += c
        },
    },
    
  • 子组件在自己的配置对象中,设置props接收父组件传递的函数

    // 子组件接收父组件传递props
    props: ['fn'],
    
  • 在子组件中调用接收到的函数,并向函数中传递数据。父组件中通过函数的参数接收到数据并使用数据。

    <h2 @click="fn(100)">我是Child 2组件</h2>
    

注意

  • 父组件传递给子组件的函数,必须写在methods中。

    原因:写在methods中的函数,他们的this会被强制修改给当前的组件的实例对象,如果不写在methods中,那么该函数的this就会随着调用者的改变发送变化。

    原理:只要写在了methods中掉函数,Vue底层都会对他们使用bind来强制修改this指向。bind修改过的this指向,是无法被二次修改的!

总结

props中,父传子,父组件传递的是真正的数据

​ 子传父,父组件传递的是接收数据的函数

2.provide/inject(频率低)

provide/inject通讯方式,适用于祖孙之间

基本流程

  • 在祖先组件的配置对象中,添加provide配置属性,属性值可以是对象或者函数

     // provide实现祖孙数据传递,provide可以以 对象的方式 或 函数的方式传递
      // provide: {
      //   aaa: '111',
      //   bbb: '222',
      // },
      // 如果要获取app组件中的数据到provide中,必须要用函数的方式定义数据,不然this会指向undefine而不是组件实例
      provide() {
        return {
          aaa: '111',
          bbb: '222',
          obj: this.obj,
          getNum: this.getNum,
        }
      },
    
  • 在后代组件的配置对象中,添加inject配置属性,属性值是Array<string>字符串数组类型,内部的字符串就是祖先中的provide内传递是属性名。

     // 在孙组件中使用inject可以接收祖先组件中定义的provide中的值
     inject: ['aaa', 'bbb'],
    

注意

  • 如果provide中配置的数据,是data中的某个数据,那么只能使用函数的写法。

    原因:是对象形式创建provide的话,会在组件配置对象创建时就也立刻创建,而这是组件实例对象还没有产生,this指向undefined,provide中无法获取到data中的值。而使用函数写法的provide会在data函数执行之后才执行。

  • provide/inject是可以实现祖先向后代组件传递数据,也可以实现后代组件向祖先组件传递数据。

    如果要想实现后代组件向祖先组件传递数据

    必须满足三个条件:

    1. 必须在祖先组件中的data中声明一个对象
    2. 必须通过provide函数暴露这个对象
    3. 后代组件只能修改对象中的属性
    

3.自定义事件通讯(频率高)

自定义事件方式通讯,适用于 子传父

回顾事件

事件,是指在程序运行过程中,发生的事情或者触发的操作。

事件的三要素

  • 事件源
  • 事件名(事件类型)
  • 事件处理器(又称事件监听,事件回调函数)

vue中事件分为两类:1.原生事件 2.自定义事件

原生事件

原生事件是由W3C推出的一套事件机制,一个标签具有哪些原生事件,都是W3C机构指定的,我们开发者无法修改。

  • 在vue中绑定原生事件
    • 在原生标签上使用@符绑定
    • onclick或者addEventListener('click')
    • 在组件标签上使用@符绑定并用native修饰符,来给组件 的根节点绑定原生事件。
  • 触发原生事件
    • 由于事件是W3C推出的,浏览器负责实现的,所以事件的触发由浏览器负责。

自定义事件

自定义事件是由Vue推出的一套事件机制,一个组件具有什么自定义事件,都是开发者自己随意取名定义的。

  • 在vue中绑定自定义事件

    • 在组件标签上使用@符号绑定

      <Child1 @fn="(data) => {console.log(data)}"></Child1> 
      
    • 通过this.$on语法绑定

      // 配合ref获取标签
      <Test ref="aaa"></Test>
      
      this.$refs.aaa.$on('fn',(data) => {console.log(data)})
      
  • 触发

    • 由于事件是Vue推出的,事件名也是开发者自己定义的,所以事件的触发由开发者自己触发
    • 语法:this.$emit('事件名称',数据)

无论是原生事件还是自定义事件,在Vue中都可以使用@绑定事件

区分

原生事件与自定义事件在vue中都可以通过@+事件名绑定,并且自定义事件的事件名可以自定义 ,所以无法根据绑定方式区分。但可以根据事件源来区分。

  • 如果一个事件绑定在原生html标签上,那么这个事件就是原生事件 。
  • 如果一个事件绑定在组件标签上,那么这个事件就是自定义事件

与自定义事件相关的vue实例事件

  • vm.$on,该方法可以给指定的组件实例绑定自定义事件,并添加事件处理器。
  • vm.$once ,该方法可以给指定的组件实例绑定自定义事件,并添加事件处理器,但是该事件只能触发一次,触发之后会自动解绑
  • vm.$emit,该方法可以触发当前实例上的所有事件
  • vm.$off,该方法可以解绑自定义事件
    • 该方法可以有0-2个参数
    • 如果不传任何参数,解绑当前组件实例上的所有事件
    • 如果只传入一个参数,解绑当前组件实例身上指定的事件的所有事件处理器
    • 如果传入两个参数,解绑当前组件实例身上指定的事件的指定事件处理器

v-model通讯

在组件标签上可以使用v-model实现通讯。

v-model的使用

  • 在原生表单标签上使用,如input标签,将标签中的value指与data中的msg双向数据绑定。

    <input type="text" v-model="msg" /> -->
    <!-- vue中的v-model使用的input事件,可以通过lazy修饰符,切换为change事件,change事件只有当失去焦点才触发 -->
    <input type="text" v-model.lazy="msg" />
    <!-- 在原生html标签上使用v-model的实现原理: -->
    <input type="text" :value="msg" @input="handleInput" /> 
    
     methods: {
        handleInput(e) {
          this.msg = e.target.value
        },
      },
    
  • 在组件标签上使用,实现双向数据传递

    <!-- 会往Child1组件中传递一个props数据和一个自定义事件 -->
    <Child1 v-model="msg"></Child1>
    
    <!-- 在vue组件标签上使用v-model的实现原理 -->
    <Child1 :value="msg" @input="(data) => (msg = data)"></Child1>
    
  • 在组件标签中绑定的v-model,在该组件内可以接收到一个默认属性名为value的props数据,和一个默认事件名为input的自定义函数

    // 在vue的组件标签中绑定的v-model,在该组件内可以接受到一个默认值为value的props和事件名为input的自定义事件
      props: ['value'],
      mounted() {
        this.$emit('input', '子组件传递的数据')
      },
    }
    

拓展:可以在接收数据的组件中通过model配置项来修改默认的props数据属性名,和自定义事件名

// 在子组件中还可以配置model属性,设置默认收到的组件实例上的v-model传递的名称
  model: {
    // prop修改的是默认传递来的props的属性名
    prop: 'value1111',
    // event修改的是默认传递来的自定义事件名
    event: 'input1111',
  },

  // 在vue的组件标签中绑定的v-model,在该组件内可以接受到一个默认值为value的props和事件名为input的自定义事件
  props: ['value1111'],
  mounted() {
  	this.$emit('input1111', '子组件传递的数据')
  },
}

sync修饰符

  • 在组件实例绑定数据传递props时,添加.sync修饰符,可以实现双向数据绑定,与v-model的功能大致相同。
<Child1 :msg.sync="msg"></Child1>
  • 在接收数据的组件中,与v-model类似,接收到一个props数据,一个自定义事件

    但是props数据的属性名是传递时的属性名,而不是默认值

    自定义事件的事件名为update:+props接收的属性名,而不是默认值

    props: ['msg'],
    mounted() {
      this.$emit('update:msg', '子组件的数据')
    },
    

扩展:由于sync修饰符的功能与v-model几乎相同,所以Vue3中将.sync修饰符删除了。但是留下来的v-model指令的核心其实是sync的

全局事件总线

全局事件总线的几要素

  • 角色

    • 订阅者,想要数据的人,就是订阅者
    • 发布者,拥有数据的人,就是发布者
  • 操作

    • 订阅
    • 发布
    • 解绑订阅
  • 约束

    • 订阅者和发布者必须同时存在
    • 订阅操作必须在发布之前
  • 流程

    // 1.在Vue的原型对象上添加$bus属性,并在内部存放一个Vue的实例对象
    new Vue({
      render: h => h(App),
      beforeCreate(){
          Vue.prototype.$bus = this
      }
    }).$mount('#app')
    
    // 2.订阅方组件,需要执行订阅操作
    mounted() {
        this.$bus.$on('getNum', (num)=>{console.log('getNum',num)})
    }
    
    // 3.发布方组件,需要执行发布操作
    this.$bus.$emit('getNum',123)
    
    // 4.解绑订阅,订阅方在即将卸载阶段,要将订阅解绑
    beforeDestroy() {
       // 在即将挂载阶段,接收数据的组件要解除绑定的事件总线自定义函数
       this.$bus.$off('getNum')
    },
    

注意

  • 属性名可以不用是$bus,是任意起名的
  • bus中必须存放的是Vue实例,否则无法调用bus中必须存放的是Vue实例,否则无法调用on或$emit方法
  • 使用了全局事件总线,就要记得解绑订阅,防止宕机

4.vm实例属性通讯(频率低)

通过vm实例上的属性找到对应的组件实例对象,从而进行通讯

  • vm.$parent,数据类型:Vue Instance

    mounted() {
        // 可以在子组件中使用组件实例上的$parent拿到父组件的实例对象,从而拿到父组件中的数据
        console.log(this.$parent)
    },
    
  • vm.$root,数据类型:Vue Instance

    • 通过当前组件实例对象身上的$root属性,可以快速找到当前项目的根组件实例对象。就是main.js中,new Vue得到的实例对象
  • vm.$children,数据类型:Array<Vue Instance>

    • 可以获取到当前组件的子组件实例对象组成的数组。
  • vm.$refs,必须配合标签属性ref使用

    • 只要被ref标记过的标签,在$refs对象中,多可以快速找到,而且ref标签属性的值,会称为$refs的属性名

    • 如果被ref标记的是一个原生标签,那么$refs中存储的就是该标签的真实DOM

      <h1 ref="h1">App</h1>
      
      mounted(){
          // 获取的是h1真实DOM
          console.log(this.$refs.h1)
      }
      
    • 如果被ref标记的是一个组件标签,那么refs中存储的就是该组件标签的组件实例

      <Child1 ref="c1"></Child1>
      
      mounted(){
        // 可以获取到Child1组件实例上的count数据
        console.log(this.$refs.c1.count)
      }
      

5.attrs和listeners(频率低)

组件实例上的属性,用来接收父组件传递下来的数据。

  • vm.$attrs,数据类型:对象

    • 内部会存储props没有接收,但是也传递下来的剩余数据
    • $attrs将标签属性的属性名作为对象的属性名,标签属性的值作为属性值进行存储
    // App组件中
    <Child1 a="1" b="2" c="3"></Child1>
    
    // Child1组件中
     props: ['a'],
     mounted() {
        // vm.$attrs可以获取当前组件中没有被props接收但是也传递过来的数据
        // console.log(this.$attrs) // { b:'2' , c:'3' }
     },
    

扩展:往标签上绑定数据v-bind不仅可以写某个标签的属性名,还可以直接接收一整个对象。

会自动将该对象解构,对象的属性名会变成标签属性名,对象属性值会变为标签属性值进行展开

 <Child1
      v-bind="{
        aaa: '111',
        bbb: '222',
      }"
 ></Child1>

可以与attrs配合使用,在子组件中,直接将attrs配合使用,在子组件中,直接将attrs绑定在v-bind后,可以快速将当前组件不用的数据,传递到后代组件中

<GrandChild11 v-bind="$attrs"></GrandChild11>

优点:不再需要关注一共得到了多少个数据,我们只需要关心当前组件需要用多少个。

  • vm.$listeners,数据类型:对象

    • 内部会存储当前组件的所有的自定义事件
    • $listeners会将自定义事件的名称作为对象的属性名,自定义事件的回调函数作为属性值进行存放
    // App组件
    <Child2 @click="handleClick" @fn2="() => {}" @fn3="() => {}"></Child2>
    
    // Child2组件
    mounted() {
        // 在组件实例中有$listeners可以获取父组件给该组件绑定的所有自定义事件
        console.log(this.$listeners)
    },
    

扩展

  • v-on指令是绑定事件,也可以接收一个对象,对象内部的属性名会自动称为事件名,对象的属性值会成为事件的回调函数

  • v-on指令会自动识别当前是和在什么标签上使用

    • 如果v-on在原生标签上使用,绑定的一定是原生事件
    • 如果v-on在组件标签上使用,绑定的一定是自定义事件(.native修饰除外)
    <div class="hello">
        <!-- @是v-on的缩写,使用v-on绑定事件还可以绑定一个对象,会自动将对象解构,属性名就是绑定的事件名 -->
        <!-- <h2
          v-on="{
            click: handleClick,
          }"
        >
          我是Child 2组件
        </h2> -->
        <!-- 可以直接使用v-on在子组件中绑定listeners中的事件 -->
        <h2 v-on="$listeners">我是Child 2组件</h2>
        <GrandChild21 v-on="$listeners"></GrandChild21>
    </div>
    

6.插槽

插槽是所有通讯方式中,最特殊的一个。

其他的通讯方式都是用于传递某些数据,而插槽是用来传递页面结构的

  • 默认插槽

    • 父组件在子组件标签中插入一段页面结构
    <Son>
        <h2>默认插槽</h2>
        <h2>我是组件内子元素</h2>
        <h2>hello slot</h2>
    </Son>
    
    • 子组件中使用slot组件标签来显示父组件传递下来的结构
    <div>
        <h1>Son组件</h1>
        <!-- 默认插槽中,每一个slot标签都是完整的所有的子节点 -->
        <slot></slot>
    </div>
    
  • 具名插槽

    • 父组件在子组件标签中插入一段页面结构,同时需要使用v-slot指令,给当前的插槽去一个别名
    <Son>
        <template v-slot:top>
            <h2>v2.6.0后的具名插槽写法</h2>
        </template>
    </Son>
    
    • 子组件中使用slot组件标签来显示父组件传递下来的结构,必须给slot组件添加标签属性name,用于声明显示的是哪个一个插槽,属性值就是父组件传递的别名
    <div>
        <h1>Son组件</h1>
        <slot name="top"></slot>
    </div>
    
  • 作用域插槽

    • 父组件在子组件标签中插入一个页面结构,需要使用v-slot指令,来给当前的插槽取一个别名

    • 子组件中使用slot组件标签来显示父组件传递下来的结构,必须给slot组件添加标签属性name,用于声明显示的是哪个一个插槽,属性值就是父组件传递的别名

    • 子组件可以将自己的数据,通过标签属性形式,传给slot组件,slot组件会将接收到的所有的标签属性,生成一个类似于props的对象,传给指定的插槽

    <div>
        <slot :arr="arr" :msg="msg"></slot>
    </div>
    
    • 在插槽的v-slot指令之后,书写="变量名",用于接收传递过来的数据
    <Son>
        <!-- 作用域插槽必须要用template包裹子节点,并通过scope数据可以接收到slot中传过来的数据,是一个对象 -->
      	<!-- 'v-slot:' 还可以简写为 '#' , scope是一个对象也可以解构为{arr,msg}-->
        <!--  <template #top="{arr,msg}"> -->
        <template v-slot:top="scope">
            <h1>{{ scope.msg }}</h1>
            <ul>
                <li v-for="(item, index) in scope.arr" :key="index">
                    {{ item }}
                </li>
            </ul>
        </template>
    </Son>
    

7.VueRouter中的路由传参

一般分为三种传参方式:query,params,meta。详细请查看vue-router篇。

8.Vuex

详细请查看vuex篇