前端学习之路--Vue组件通信

179 阅读5分钟

组件通信

  组件之间的通信主要分为三种情况:父子组件之间通信、兄弟组件之间通信、跨级通信

  首先,官方文档推荐了Vuex插件,在此就不讲述Vuex的通信方式了,关于Vuex的入门我在上一篇文章Vuex入门(新手向)中讲过,在这里只讲原生Vue的方式。

父子组件之间传递

父组件向子组件通信

方法一:利用props

  props在Vue官方文档中的描述就是用于接收来自父组件的数据,在子组件中,如果props声明的名称与子的data一致,会报错,data无法显示。

  prop是properties的缩写,就是属性的意思。通过props可以在组件上注册的一些自定义特性,为单向数据流,父级prop的更新会向下流动到子组件中,反过来不行。

props声明的属性(properties)与v-bind绑定的属性(attributes)有什么区别?v-bind.prop修饰符又有什么用?

先说结论:props声明的properties是父组件中v-bind声明的attributes的映射。

properties由DOM定义,而attributes由html定义。一些html的attributes与properties可以一一映射,比如id属性,有些属性无法一一对应,不能把properties和attributes都翻译为属性,不能混淆在一起。attributes会初始化DOM的properties。例如input标签中设置value=“a”,html中input的attributes-value是a,当html被浏览器渲染后,会创建DOM节点,此时properties-value被初始化为a,但是当用户在input中输入b时,其DOM的properties-value就变成了b,而其html的attributes-value依旧是a。input.getAttribute('value')返回a。可以通过v-bind.prop修饰符来让属性绑定到property上。

  • 传值

  整体来说就是在子组件中设置props指向父组件中绑定的属性,具体方式是在子组件中设置props声明需要从父组件中接收的数据,该数据通过一个特性名来表示。当父组件调用子组件时,在组件标签上v-bind绑定一个属性,该属性名就是子组件props的特性名。

示例代码如下:

//父组件
<template>
    <div class="box" id="father">
          <!--这里是父组件-->
          <Child 
            :communicate="msg">
            <!-- 这里communicate是一个属性名,与子组件声明的props中的名一致;msg的值与父组件的data内容一致,是Vue的双向绑定 -->
          </Child>
    </div>
</template>
<script>
    import Child from './Child.vue' 
    export default {
        name: "father",
        data (){
            return {
                msg:'message from father'
            }
        }       
    }
</script>

//子组件
<template>
    <div id="child">
          <!--这里是子组件-->
          <!--这里communicate就是props接收的数据-->
          {{communicate}}
    </div>
</template>
<script>
    export default {
        name: "child",
        data (){
            return {
                
            }
        },
        props:{
            //这里communicate就是props声明的特性名,用来获取来自父级的数据
            communicate:{
                type:String,
                required:true
            }
        }
       
    }
</script>
  • 传引用

  与props传值方式类似,只不过在prop验证时,props的属性名如果是数组或对象这种引用类型的话,其默认值必须从一个工厂函数获取。

propA: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function () {
        return { message: 'hello' }
      }
    },
propB: {
      type: String,
      //数据类型的默认值可以直接作为default的value
      default:'abc'
      required: true
    }

方法二:直接访问子组件(子元素)

  • 父链 $children

  父组件中可以利用this.$children来访问父组件中的所有子组件,并且可以递归下去到子组件的子组件,一直到最内层的组件,像一条链子一样。在接下来的子组件向父组件通信中也可以使用父链的另一个api实现

  • 子组件索引 $refs

  当子组件较多时,通过$children来访问子组件就比较复杂了,当子组件较多时可以在子组件中设置索引ref="索引名",父组件中利用this.$refs.索引名来访问子组件传递事件。

子组件向父组件通信

方法一:利用v-on

  子组件与父组件通信一般就是传递事件回调,Vue中也有类似JS中的观察者模式(addEventListener)的监听模式,具体来说就是在子组件中注册自定义事件$emit('事件名','事件内容'),在父组件中设置事件@事件名,这个事件名里的函数参数就是子组件中声明的事件内容。同时父组件中用$on()来监听。

示例代码如下:

//父组件
<template>
    <div class="box" id="father">
          <!--这里是父组件-->
          <Child 
            @eventSelf="fn">
            <!-- 这里eventSelf是在子组件中自定义的事件名,fn是该事件所调用的方法,在父组件的methods中声明 -->
          </Child>
    </div>
</template>
<script>
import Child from './Child.vue';
export default {
    components: {
        Child
    },
    methods: {
        fn (val) {
            console.log(val);
        }
    }
}
</script>

//子组件
<template>
    <div id="child">
          <!--这里是子组件-->
          <!--这里的click是通过v-on监听的原生button的点击事件,最初触发的就是click,之后才进行通信。handler是click事件的调用方法,在子组件的methods中声明-->
          <button @click="handler">Click me!</button>
    </div>
</template>
<script>
    export default {
        name: "child",
        data (){
            return {
                
            }
        },
        props:{
            //这里fn就是props声明的特性名,用来获取来自父级的数据
            fn:{
                type:String,
                required:true
            }
        },
        methods:{
            handler:function(){
                //这里注册自定义事件,事件名命名为eventSelf,具体事件用到的方法是在父组件中声明的。
                this.$emit('eventSelf')
            }
        }
       
    }
</script>

方法二:父链$parent

  前面讲父组件向子组件通信时用到了$children的方式,同样,当子组件向父组件通信时,可以使用$parent来无限递归访问,知道根组件为止。

方法三:利用v-model

  这种方法其实是方法一的特殊情况,就是子组件注册自定义事件名为input的时候,$emit('input',事件内容),按照方法一的方式,应该在父组件上监听事件@input=“父事件名”,由于v-model是与input双向绑定的一个语法糖,所以可以通过设置v-model=“value”间接来实现父子通信,一般这种方法只用于表单中。

示例代码如下:

//父组件
<template>
    <div class="box" id="father">
          <!--这里是父组件-->
          <p>总数:{{total}}</p>
          <Child 
            v-model="total">
            <!-- 这里v-model就是监听input事件以更新数据,就相当于@input,total是监听的数据,也就是input的value值,它的改变将导致data中的total改变,继而影响p标签中绑定的total -->
          </Child>
    </div>
</template>
<script>
import Child from './Child.vue';
export default {
    components: {
        Child
    },
    data (){
        return{
            total:0
        }
    }
    methods: {
    }
}
</script>

//子组件
<template>
    <div id="child">
          <!--这里是子组件-->
          <!--这里的click是通过v-on监听的原生button的点击事件,最初触发的就是click,之后才进行通信。handler是click事件的调用方法,在子组件的methods中声明-->
          <button @click="handler">Click me!</button>
    </div>
</template>
<script>
    export default {
        name: "child",
        data (){
            return {
                counter:0
            }
        },
        methods:{
            handler:function(){
                this.counter++;
                //这里注册自定义事件,事件名命名为input,input的value就是counter。
                this.$emit('input',this.counter)
            }
        }
       
    }
</script>

兄弟组件通信

  通过上面讲述的父子组件的通信,可以容易想到兄弟组件A、B之间的通信可以通过一个共同父组件C或者共同子组件D来作为中介进行A到C/D,C/D再到B之间中转通信。

  在Vue.js 2.x中,可以使用一个空的Vue实例作为通信所需的中介组件,这个空的Vue实例就叫做中央事件总线(bus)。具体实施方式就是首先创建一个空Vue实例(bus),里面可以没有任何内容,利用bus.on和bus.emit作为中介来中转两个组件之间的通信。

示例代码如下:

//一般会把bus组件抽离出来放在main.js或者App.vue中
import Vue from 'vue'
const Bus = new Vue()
export default Bus

组件调用时引入

<template>
    <div id="child">
          <!--这里是组件A-->
          <button @click="handler">传递事件</button>
    </div>
</template>
...
import Bus from './Bus'
export default {
    data() {
        return {
            .........
            }
      },
  methods: {
        handler:function(){
            Bus.$emit('事件名1', ‘来自组件A的消息’)
        }
        
    },
  }

<template>
  <div id="child">
        <!--这里是组件B-->
        <p>{{content}}</p>
  </div>
</template>
...
import Bus from './Bus'
export default {
  data() {
      return {
          .........
          }
    },
    //注意这里在mounted中声明,在dom渲染前加载。
  mounted(){
      var _this = this;
      Bus.$on('事件名1',msg =>(
          _this.content = msg
      ))
  }
}

跨级组件通信

方法一:父链$parent、$children

这种方式在前面已有所讲述,就是逐级递归,一层一层的传递

方法二:如果只跨了两级,也就是爷孙组件,可以将子组件作为一个中介传递。

方法三:provide / inject

这个api是Vue2.2新增的,可以在祖先组件中通过provider来提供变量,然后在后代组件中通过inject来注入变量。