zoukankan      html  css  js  c++  java
  • Vue 组件间的通信

    什么是Vue组件?

    组件(Component)是 Vue.js 最强大的功能之一。

    组件可以扩展 HTML 元素,封装可重用的代码。

    组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树:

    组件关系 

    Vue 组件通信.png

    上面展示的图片可以引入所有 Vue 组件的关系形式:
    • A 组件和 B 组件、B 组件和 C 组件、B 组件和 D 组件形成了父子关系
    • C 组件和 D 组件形成了兄弟关系
    • A 组件和 C 组件、A 组件和 D 组件形成了隔代关系(其中的层级可能是多级,即隔多代)

    组件通信

    1、props 和 $emit

    使用props,父组件可以使用props向子组件传递数据。

    父组件

    <template>
        <son :message="son"></son>
    </template>
    
    <script>
    
    import son from './son.vue';
    
    export default {
        components: {
            son
        },
        data () {
            return {
                message: 'father message';
            }
        }
    }
    </script>

    子组件

    <template>
        <div>{{message}}</div>
    </template>
    
    <script>
    export default { 
      /**
       * 得到父组件传递过来的数据
       * 这里的定义最好是写成数据校验的形式,免得得到的数据是我们意料之外的
       *
       * props: {
       *   message: {
       *     type: String,
       *     default: ''
       *   }
       * }
       *
      */
      props:['message'], 
    }
    </script>

    父组件向子组件传递事件方法,子组件通过$emit触发事件,回调给父组件。

    父组件

    <template>
        <son @getChildData="getChildData"></son>
    </template>
    
    <script>
    
    import son from './son.vue';
    
    export default {
        components: {
            son
        },
        methods: {
            // 执行子组件触发的事件
            getChildData(val) {
              console.log(val);
            }
        }
    }
    </script>    

    子组件

    <template>
        <div>
          <input type="text" v-model="myMessage" @input="passData(myMessage)">
        </div>
    </template>
    
    <script>
    export default {
        props: {
            msg: {
                type: String,
                required: true
            }
        },
        data() {
          return {
            // 这里是必要的,因为你不能直接修改 props 的值
            myMessage: this.message
          }
        },
        methods () {
            passData(val) {
               // 数据状态变化时触发父组件中的事件
               this.$emit('getChildData', val);
            }
        }
    }
    </script>

    在上面的例子中,有父组件 father 和子组件 son。

    • 父组件传递了 message 数据给子组件,并且通过v-on绑定了一个 getChildData 事件来监听子组件的触发事件;
    • 子组件通过 props 得到相关的 message 数据,然后将数据缓存在 data 里面,最后当属性数据值发生变化时,通过 this.$emit 触发了父组件注册的 getChildData 事件处理数据逻辑。

     2、$attrs 和 $listeners

    第一种方式处理父子组件之间的数据传输有一个问题:如果父组件A下面有子组件B,组件B下面有组件C,这时如果组件A想传递数据给组件C怎么办呢?
    如果采用第一种方法,我们必须让组件A通过prop传递消息给组件B,组件B在通过prop传递消息给组件C;要是组件A和组件C之间有更多的组件,那采用这种方式就很复杂了。Vue 2.4开始提供了$attrs和$listeners来解决这个问题,能够让组件A之间传递消息给组件C。

    • $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。

    • $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件

    父组件

    <template>
        <div>
            <p>我是父组件</p>
            <son :son="son" :grandSon="grandSon" @getSonData="getSonData" @getGrandSonData="getGrandSonData"></son>
        </div>
    </template>
    
    <script>
    import son from './son.vue';
    
    export default {
        components: {
            son
        },
        data() {
            return {
                son: '爷爷的儿子',
                grandSon: '爷爷的孙子'
            }
        },
        methods: {
            // 来自子组件触发的事件
            getSonData(val) {
                console.log(val)
            },
            // 来自孙组件触发的事件
            getGrandSonData(val) {
                console.log(val)
            }
        }
    }
    </script>

    子组件

    <template>
        <div>
            <p>我是子组件</p>
            <input type="text" v-model="myMessage" @input="passData(myMessage)">
            <grandson :child="child" v-bind="$attrs" v-on="$listeners"></grandson>
        </div>
    </template>
    
    <script>
    import son from './son.vue';
    
    export default {
        components: {
            grandson
        },
        props: ['son'],
        data() {
            return {
                child: '爸爸的儿子',
                myMessage: ''
            }
        },
        // 默认为true,如果传入的属性子组件没有prop接受,就会以字符串的形式出现为标签属性
        // 设为false,在dom中就看不到这些属性,试一下就知道了
        inheritAttrs: false,
        created () {
            // 在子组件中打印的$attrs就是父组件传入的值,刨去style,class,和子组件中已props的属性
            console.log(this.$attrs) // 打印爷爷的孙子
        },
        methods:{
            passData(val) {
                // 触发父组件中的事件
                this.$emit('getSonData', val)
            }
        }
    }
    </script>

    孙组件

    <template>
        <div>
            <p>我是孙组件</p>
            <input type="text" v-model="myMessage" @input="passData(myMessage)">
            {{$attrs.grandSon}}
            {{$attrs.child}}
        </div>
    </template>
    
    <script>
    export default {
        data() {
            return {
                myMessage: ''
            }
        },
        inheritAttrs: false,
        created () {
            // 打印爷爷的孙子和爸爸的儿子
            console.log(this.$attrs)
        },
        methods:{
            passData(val) {
                // 触发父组件中的事件
                this.$emit('getGrandSonData', val)
            }
        }
    }
    </script>

    在上面的例子中,我们定义了 父,子,孙 三个组件,其中组件子是组件父的子组件,组件孙是组件子的子组件。

    • 孙组件中能直接触发getGrandSonData的原因在于子组件调用孙组件时 使用 v-on 绑定了$listeners 属性
    • 通过v-bind 绑定$attrs属性,孙组件可以直接获取到父组件中传递下来的props(除了子组件中props声明的)

    3、v-model

    父组件通过v-model传递值给子组件时,会自动传递一个value的prop属性,在子组件中通过this.$emit(‘input', val)自动修改v-model绑定的值

    父组件

    <template>
        <div>
            <p>我是父组件</p>
            {{message}}
            <son v-model="message"></son>
        </div>
    </template>
    
    <script>
    import son from './son.vue';
    
    export default {
        components: {
            son
        },
        data() {
            return {
                message: '我的儿子'
            }
        }
    }
    </script>    

    子组件

    <template>
        <div>
            <p>我是子组件</p>
            <input type="text" v-model="myMessage" @input="passData(myMessage)">
        </div>
    </template>
    
    <script>
    export default {
        //v-model会自动传递一个字段为value的prop属性 
        props: ['value'],
        data() {
            return {
                myMessage: this.message
            }
        },
        methods:{
            passData(val) {
                //通过如此调用可以改变父组件上v-model绑定的值 
                this.$emit('input', val)
            }
        }
    }
    </script>
        

    在上面的实例代码中,我们定义了 father 和 son 两个组件,这两个组件是父子关系,v-model 也只能实现父子组件之间的通信。

    • 在 father 组件中,我们给自定义的 son 组件实现了 v-model 绑定了 message 属性。此时相当于给 son 组件传递了 value 属性和绑定了 input 事件。
    • 在定义的 son 组件中,可以通过 props 获取 value 属性,根据 props 单向数据流的原则,又将 value 缓存在了 data 里面的 myMessage 上,再在 input 上通过 v-model 绑定了 myMessage 属性和一个 input 事件。当 input 值变化时,就会触发 input 事件,处理 father 组件通过 v-model 给 son 组件绑定的 input 事件,触发 father 组件中 message 属性值的变化,完成 child 子组件改变 father 组件的属性值。

    4、provide和inject

    父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量。不论子组件有多深,只要调用了inject那么就可以注入provider中的数据。而不是局限于只能从当前父组件的prop属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。

    父组件

    <template>
        <div>
            <p>我是父组件</p>
        </div>
    </template>
    
    <script>
    export default {
        provide:{
            message: '中午一起吃饭'
        }
    }
    </script>

    子组件

    <template>
        <div>
            <p>我是子组件</p>
            {{message}} //会显示中午一起吃饭
        </div>
    </template>
    
    <script>
    export default {
        inject:['message'],//得到父组件传递过来的数据
    }
    </script>

    在上面的实例中,我们定义了组件 father 和组件 son,组件 father 和组件 son 是父子关系。

    • 在 father 组件中,通过 provide 属性,以对象的形式向子孙组件暴露了一些属性
    • 在 son 组件中,通过 inject 属性注入了 father 组件提供的数据,实际这些通过 inject 注入的属性是挂载到 Vue 实例上的,所以在组件内部可以通过 this 来访问。

    5、$parent 和 $children

    这里要说的这种方式就比较直观了,直接操作父子组件的实例。$parent 就是父组件的实例对象,而 $children 就是当前实例的直接子组件实例了,不过这个属性值是数组类型的,且并不保证顺序,也不是响应式的。

    父组件

    <template>
        <div>
            <p>我是父组件</p>
            <button @click="changeChildValue">test</button>
            <son></son>
            <grandson></grandson>
        </div>
    </template>
    
    <script>
    import son from './son.vue'
    import grandson from './grandSon.vue'
    export default {
      components: {
        son,
        grandSon
      },
      data() {
        return {
            fatherMessage: ''
        }
      }
      methods: {
        changeChildValue(){
          this.$children[0].sonMessage = 'hello';
        }
      }
    }
    </script>

    子组件

    <template>
        <div>
            <p>我是子组件</p>
            <input type="text" v-model="mymessage" @change="changeValue" /> 
        </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          mymessage: this.$parent.fatherMessage
        }
      },
      methods: {
        changeValue(){
          this.$parent.fatherMessage = this.mymessage;//通过如此调用可以改变父组件的值
        }
      }
    }
    </script>
    在上面实例代码中,分别定义了 father 和 son 组件,这两个组件是直接的父子关系。两个组件分别在内部定义了自己的属性。在 father 组件中,直接通过 this.$children[0].sonMessage = 'hello';son 组件内的 sonMessage 属性赋值,而在 son 子组件中,同样也是直接通过this.$parent.fatherMessagefather 组件中的 fatherMessage 赋值,形成了父子组件通信。

    6、 中央事件总线

    对于父子组件之间的通信,上面的两种方式是完全可以实现的,但是对于两个组件不是父子关系,那么又该如何实现通信呢?在项目规模不大的情况下,完全可以使用中央事件总线 EventBus 的方式。 

    在组件之外定义一个bus.js作为组件间通信的桥梁,适用于比较小型不需要vuex又需要兄弟组件通信的

    1. bus.js中添加如下
     import Vue from 'vue'
     export const Bus = new Vue();
    2.组件中调用bus.js通过自定义事件传递数据
    import Bus from './bus.js' 
      export default { 
          methods: {
             bus () {
                Bus.$emit('msg', '我要传给兄弟组件们')
             }
          }
      }
    3.兄弟组件中监听事件接受数据
    import Bus from './bus.js'
    export default {
        mounted() {
          Bus.$on('msg', (e) => {
             console.log(e)
         })
        }
    }

    中央事件总线 EventBus 非常简单,就是任意组件和组件之间打交道,没有多余的业务逻辑,只需要在状态变化组件触发一个事件,然后在处理逻辑组件监听该事件就可以。

  • 相关阅读:
    spring加载bean实例化顺序
    Java生成CSV文件实例详解
    JSch
    socket(一)
    Core Data
    运行时c函数
    ReactiveCocoa(RAC)
    先来个xmpp学习连接
    FMDB
    NSKeyedArchive(存储自定义对象)
  • 原文地址:https://www.cnblogs.com/Mr-Tao/p/11515988.html
Copyright © 2011-2022 走看看