vue组件中的通信

一、组件间的关系

1、父子关系

2、兄弟关系

3、隔代关系

 

二、组件间的通信方式

1、props
2、$emit/$on
3、VUEX
4、$parent/$children
5、$attrs/$listeners
6、provide/inject
 
三、通信方式举例
 
新建了一个过程,采用webpack来管理项目。

 

 方法一:props / $emit

1、props---父组件向子组件传值
子组件:sub1.vue
父组件:app.vue
父组件通过props向下传递给子组件。注:组件中的数据共有三种形式:data,props,computed
// app.vue   父组件

<template>
    <div>
        <sub1 v-bind:data_from_app = "msg_parent"></sub1>
    </div>
</template>

<script>
import sub1 from './components/sub1.vue';

export default {
    data(){
        return {
            msg_parent:'父组件数据app'
        }
    },
    components:{
        sub1
    }
}
</script>

<style lang="scss" scoped>

</style>

  

// 子组件  sub1.vue  

<template>
    <div>
        <p>{{data_from_app}}</p>
    </div>
</template>

<script>
export default {
    data(){
        return {
            sub_msg:'子组件sub1数据',
        }
    },
    props:{
        data_from_app:{
            type:String,
            require:true
        }
    }
}

// 父组件通过props向下传递给子组件。注:组件中的数据共有三种形式:data,props,computed
</script>

<style lang="scss" scoped>

</style>

  

2、子组件向父组件传值---(通过事件的形式)--- $emit

子组件通过events给父组件发送消息,实际就是把自己的数据发送到父组件  $emit发送  $event接收

//  子组件 sub1.vue

<template>
    <div>
        <p>{{data_from_app}}</p>
        <p @click="changeText">{{text_sub}}</p>
    </div>
</template>

<script>
export default {
    data(){
        return {
            sub_msg:'子组件sub1数据',
            text_sub:'子组件原来的数据'
        }
    },
    props:{
        data_from_app:{
            type:String,
            require:true
        }
    },
    methods:{
        changeText(){
            // 自定义事件,传递值 "子组件向父组件传值" 
            this.$emit('titleChanged','子组件向父组件传值')
        }
    }
}
// 1、props
// 父组件通过props向下传递给子组件。注:组件中的数据共有三种形式:data,props,computed
</script>

<style lang="scss" scoped>

</style>

  

// 父组件 app.vue

<template>
    <div>
        <!-- 给子组件定义了一个点击事件changeText,
        在子组件中的时间定义中,使用$emit传递titleChanged和要传递给父组件参数,子组件用$emit来传递参数数据 -->
        <sub1 v-on:titleChanged="updateText" v-bind:data_from_app = "msg_parent"></sub1>
        <!-- 父组件通过$event来接收传递过来的参数 updateText($event)  -->
        <!-- 这里要与子组件 titleChanged  保持一致 -->
    </div>
</template>

<script>
import sub1 from './components/sub1.vue';

export default {
    data(){
        return {
            msg_parent:'父组件数据app',
            text:'父组件原本的text'
        }
    },
    methods:{
        updateText(e){
            this.text = e;
            console.log(this.text)
        }
    },
    components:{
        sub1
    }
}
</script>

<style lang="scss" scoped>

</style>

  

方法二、$emit / $on

这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量的实现了任何组件间的通信,包括父子、兄弟、隔代。当项目比较大时、可以考虑使用vuex。

实现方式:

var Event = new Vue()
Event.$emit(事件名,数据) ;
Event.$on(事件名,data => {});

  

<div id="itany">
    <my-a></my-a>
    <my-b></my-b>
    <my-c></my-c>
</div>
<template id="a">
  <div>
    <h3>A组件:{{name}}</h3>
    <button @click="send">将数据发送给C组件</button>
  </div>
</template>
<template id="b">
  <div>
    <h3>B组件:{{age}}</h3>
    <button @click="send">将数组发送给C组件</button>
  </div>
</template>
<template id="c">
  <div>
    <h3>C组件:{{name}},{{age}}</h3>
  </div>
</template>
<script>
var Event = new Vue();//定义一个空的Vue实例
var A = {
    template: '#a',
    data() {
      return {
        name: 'tom'
      }
    },
    methods: {
      send() {
        Event.$emit('data-a', this.name);
      }
    }
}
var B = {
    template: '#b',
    data() {
      return {
        age: 20
      }
    },
    methods: {
      send() {
        Event.$emit('data-b', this.age);
      }
    }
}
var C = {
    template: '#c',
    data() {
      return {
        name: '',
        age: ""
      }
    },
    mounted() {//在模板编译完成后执行
     Event.$on('data-a',name => {
         this.name = name;//箭头函数内部不会产生新的this,这边如果不用=>,this指代Event
     })
     Event.$on('data-b',age => {
         this.age = age;
     })
    }
}
var vm = new Vue({
    el: '#itany',
    components: {
      'my-a': A,
      'my-b': B,
      'my-c': C
    }
});    
</script>

  

$on 监听了自定义事件data-a 和 data-b,因为有时不确定何时会触发事件,一般会在mounted或created钩子中监听

 

方法三、vuex

 

 

vuex 的原理:

vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中数据时,必须通过mutation进行,mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量同步操作需要走Action,但Action是无法直接修改state的,还是需要通过mutation来修改state的数据。最后根据state的变化,渲染到视图上。

各模块的介绍:

  • Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。
  • dispatch:操作行为触发方法,是唯一能执行action的方法。
  • actions:操作行为处理模块,由组件中的$store.dispatch('action 名称', data1)来触发。然后由commit()来触发mutation的调用 , 间接更新 state。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。
  • commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
  • mutations:状态改变操作方法,由actions中的commit('mutation 名称')来触发。是Vuex修改state的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。
  • state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。
  • getters:state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象。

vuex是vue的状态管理器,存储的数据是响应式的,但是并不会保存起来,刷新之后就回到了初始状态。具体做法应该是在vuex里面数据改变时把数据拷贝一份保存到localStorage中,刷新之后,如果localstorage里面有保存的数据,取出来替换store里面的state

 

方法四:$attrs / $listeners

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

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

//  app.vue  最外层的组件,本身定义了foo boo  coo 三个数据
// 此时里面的子组件是 sub1  ,给它使用了 foo boo coo  三个数据,但 sub1 本身只定义了 foo 这个属性

<template>
    <div>
        <!-- 给子组件定义了一个点击事件changeText,
        在子组件中的时间定义中,使用$emit传递titleChanged和要传递给父组件参数,子组件用$emit来传递参数数据 -->
        <p>父组件中的数据:foo:{{foo}}、boo:{{boo}}、coo:{{coo}}</p>
        <sub1 :foo="foo"
              :boo="boo"
              :coo="coo"
              v-on:titleChanged="updateText" 
              v-bind:data_from_app = "msg_parent"></sub1>
        <!-- 父组件通过$event来接收传递过来的参数 updateText($event)  -->
        <!-- 这里要与子组件 titleChanged  保持一致 -->

        <!-- <subvue1></subvue1>
        <subvue2></subvue2> -->


    </div>
</template>

<script>
import sub1 from './components/sub1.vue';
// import subvue1 from './components/subVue/subvue1.vue';
// import subvue2 from './components/subVue/subvue2.vue';

export default {
    data(){
        return {
            msg_parent:'父组件数据app',
            text:'父组件原本的text',
            foo:'html',
            boo:'css',
            coo:'vue'
        }
    },
    methods:{
        updateText(e){
            this.text = e;
            console.log(this.text)
        }
    },
    components:{
        sub1,
        // subvue1,
        // subvue2
    }
}
</script>

<style lang="scss" scoped>

</style>

  

// sub1  APP 的子组件,本身只定义了 foo 这个属性,因此通过$attrs拿到的是 boo  coo
// 这里sub1有了一个子组件  subvue1,给这个子组件使用了$attrs,即boo  coo ,但本身子组件只有 boo 属性

<template>
    <div>
        <p>{{data_from_app}}</p>
        <p @click="changeText">{{text_sub}}</p>
        <p>foo:{{foo}}</p>
        <p>sub1的$attrs:{{$attrs}}</p>
        <subvue1 v-bind="$attrs"></subvue1>
    </div>
</template>

<script>
import subvue1 from './subVue/subvue1.vue'
export default {
    data(){
        return {
            sub_msg:'子组件sub1数据',
            text_sub:'子组件原来的数据'
        }
    },
    inheritAttrs:false,  // 可已自动关闭挂载到根元素上没有在props声明的属性
    props:{
        data_from_app:{
            type:String,
            require:true
        },
        foo:String
    },
    created(){
        console.log(this.$attrs);
    },
    methods:{
        changeText(){
            // 自定义事件,传递值 "子组件向父组件传值" 
            this.$emit('titleChanged','子组件向父组件传值')
        }
    },
    components:{
        subvue1
    }
}
// 1、props
// 父组件通过props向下传递给子组件。注:组件中的数据共有三种形式:data,props,computed
</script>

<style lang="scss" scoped>

</style>

  

//  subvue1   sub1 的子组件,本身只定义了 boo 这一个属性,因此 attrs  拿到的是 coo  

<template>
    <div>
        <h3>subvue1组件</h3>
        <p>boo:{{boo}}</p>
        <!-- <button >subvue1组件:将数据发给组件subvue2</button> -->
        <p>subvue1的$attrs:{{$attrs}}</p>
    </div>
</template>

<script>
import Vue from 'vue';

export default {
    data(){
        return {
            data1:'subvue1',
            data_from:''
        }
    },
    props:{
        boo:String
    },
    mounted(){

    },
    created9(){
        console.log(this.$attrs);
    },
    methods:{
   
    }
}
</script>

<style lang="sass" scoped>

</style>

  

 

 小结:$attrs表示没有继承数据的对象,格式为{属性名:属性值},vue2.4提供了$attrs、$listeners来传递数据与实践,跨级组件之间的通信更加简单。

$attrs 里面存放的是父组件中绑定的非props属性。$listeners 里面存放的是父组件中绑定的非原生事件。

 

方法五:provide/inject

二者需要一起使用。允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在上下游关系成立的时间里始终生效。就是:祖先组件中通过provide来提供变量,然后在子孙组件中通过inject来注入变量。

主要解决了跨级组件中通信问题,不过他的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立一种主动提供与依赖注入的关系。

// A.vue
export default {
  provide: {
    name: '浪里行舟'
  }
}

  

// B.vue
export default {
  inject: ['name'],
  mounted () {
    console.log(this.name);  // 浪里行舟
  }
}

  

provide/inject绑定并不是响应式的,这是刻意为之的。如果传入一个可监听对象,那么其对象的属性还是可响应的。所以,A.vue中name改变了,B.vue的this.name是不会改变的。

 

方法六:$parent / $children 与 ref

ref :如果在普通的dom元素上使用,引用指向的就是dom元素;如果用在子组件上,引用就指向组件实例。

$parent  /  $children  访问父、子实例

以上两种方法得到的都是组件实例,使用后可以调用组件的方法或访问数据。

弊端:无法在跨级或兄弟间通信

// component-a 子组件
export default {
  data () {
    return {
      title: 'Vue.js'
    }
  },
  methods: {
    sayHello () {
      window.alert('Hello');
    }
  }
}

  

// 父组件
<template>
  <component-a ref="comA"></component-a>
</template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;
      console.log(comA.title);  // Vue.js
      comA.sayHello();  // 弹窗
    }
  }
</script>

  

四、总结

  • 父子通信:

父向子传递数据是通过 props,子向父是通过 events($emit);通过父链 / 子链也可以通信($parent / $children);ref 也可以访问组件实例;provide / inject API;$attrs/$listeners

  • 兄弟通信:

Bus;Vuex

  • 跨级通信:

Bus;Vuex;provide / inject API、$attrs/$listeners

 

posted @ 2019-10-26 09:49  1220x  阅读(186)  评论(0编辑  收藏  举报