Vue是如何实现组件通信的

268 阅读2分钟

组件是 vue.js 最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用,那么如何做到组件的数据互相引用也就是组件通信呢? 下面是针对不同的使用场景,总结的组件通信方法:

父子组件通信

父组件通过props的方式向子组件传递数据,子组件向父组件传递通过子组件中$emit自定义事件,父组件v-on监听事件

父组件向子组件传递数据

父组件通过props传递数据给子组件

<template>                                     //App.vue父组件
  <div id="app">
    <users v-bind:users="users"></users>       //自定义名称方便子组件调用,后面传递数据名
  </div>
</template>
<script>
import Users from "./components/Users"
export default {
  name: 'App',
  data(){
    return{
      users:["Jack","Tom"]
    }
  },
  components:{
    "users":Users
  }
}

<template>                                      //users子组件
  <div class="hello">
    <ul>
      <li v-for="user in users">{{user}}</li>   //遍历传递过来的值,然后展示到页面
    </ul>
  </div>
</template>
<script>
export default {
  name: 'HelloWorld',
  props:{                                       //外部属性,接收父组件传递过来的users
    users:{
      type:Array,
      required:true
    }
  }
}
</script>

子组件向父组件传递数据

子组件通过$emit绑定自定义事件和要传递的数据:$emit('事件名','要传递的数据')
父组件通过v-on监听自定义事件,获取子组件的数据:v-on:事件名='函数' 或 @事件名='函数'

<template>                              // 父组件 app.vue
  <div id="app">                        // v-on监听子组件的titleChanged事件
    <app-header v-on:titleChanged="updateTitle" ></app-header>
    <h2>{{title}}</h2>                  // 对应父组件本身的updateTitle方法
  </div>
</template>
<script>
import Header from "./components/Header"
export default {
  name: 'App',
  data(){
    return{
      title:"传递的是一个值"
    }
  },
  methods:{
    updateTitle(e){                     // 更新title
      this.title = e;
    }
  },
  components:{
   "app-header":Header,
  }
}
</script>

<template>                              // 子组件 app-header
  <header>                              // 绑定点击事件时,执行changeTitle函数
    <h1 @click="changeTitle">{{title}}</h1>
  </header>
</template>
<script>
export default {
  name: 'app-header',
  data() {
    return {
      title:"Vue.js Demo"
    }
  },
  methods:{
    changeTitle() {                    // 传递事件名titleChanged,传递值"向父组件传值"
      this.$emit("titleChanged","向父组件传值");
    }
  }
}
</script>

任意组件通信

通过一个空的 Vue 实例作为总的事件中心,用它来触发事件和监听事件,实现了任何组件间的通信:包括父子、兄弟、跨级

var eventBus = new Vue()
eventBus.$emit(事件名,数据)         // 绑定自定义事件
eventBus.$on(事件名, data=>{})     // 监听自定义事件

有3个平级组件A、B、C,C组件如何获取A或B组件的数据

eventBus.$on监听了自定义事件data-a和data-b,使用mounted在组件挂载之后执行函数

<div id="all">
  <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 eventBus = new Vue();                   // 定义空的Vue实例
var A = {
  template: '#a',
  data() {
    return {
      name: 'tom'
    }
  },
  methods: {
    send() {
      eventBus.$emit('data-a', this.name); // 使用eventBus.$emit绑定事件
    }
  }
}
var B = {
  template: '#b',
  data() {
    return {
      age: 20
    }
  },
  methods: {
    send() {
      eventBus.$emit('data-b', this.age);
    }
  }
}
var C = {
  template: '#c',
  data() {
    return {
      name: '',
      age: ""
    }
  },
  mounted() {                       // 在挂载组件之后执行函数
   eventBus.$on('data-a',name => {  // 使用eventBus.$on监听事件
       this.name = name;
   })
   eventBus.$on('data-b',age => {
       this.age = age;
   })
  }
}
var vm = new Vue({
  el: '#all',
  components: {
    'my-a': A,
    'my-b': B,
    'my-c': C
  }
});
</script>

任意组件通信(使用Vuex)

Vuex是专门为vue.js开发的状态管理工具,在全局拥有一个State存放数据,当组件要更改State中的数据时
必须使用Mutation,异步操作使用Action提交Mutation,最后根据State的变化,渲染到视图上

关于Vuex的内容点击我的另一篇文章: Vuex 入门

Vuex 配合 localStorage 使用

Vuex存储的数据是响应式的,但并不会保存,刷新之后就回到了初始状态
在Vuex的数据改变时把数据拷贝一份到localStorage里面,每次使用都从localStorage里取出保存的数据
替换store里的state,数据改变之后在从新存入到localStorage

注意事项:Vuex里,我们保存的状态都是数组,而localStorage只支持字符串,所以需要用JSON转换
JSON.stringify();                             // array -> string
JSON.parse(window.localStorage.getItem());    // string -> array

let defaultCity = "上海"
try {                                         // 防止用户关闭本地存储功能还能继续执行代码
  if (!defaultCity){
    defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
  }
}catch(e){}
export default new Vuex.Store({
  state: {
    city: defaultCity
  },
  mutations: {
    changeCity(state, city) {
      state.city = city
      try {                                  // 数据改变时从新存一份到localStorage
      window.localStorage.setItem('defaultCity', JSON.stringify(state.city));
      } catch (e) {}
    }
  }
})

任意组件通信(使用 attrsattrs和 listeners)

$attrs:包含了父作用域中不作为prop被识别的特性绑定 (class和style除外)
当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定 (class和style除外)
并且可以通过v-bind="$attrs"传入内部组件,在创建高级别的组件时非常有用

$listeners:包含了父作用域中的 (不含.native修饰器的) v-on事件监听器
它可以通过v-on="$listeners"传入内部组件——在创建更高层次的组件时非常有用

Vue.component('C',{
    template:`
      <div>
        <input type="text" v-model="$attrs.messagec" @input="passCData($attrs.messagec)">
      </div>
    `,
    methods:{
      passCData(val){              // 触发父组件A中的事件
        this.$emit('getCData',val)
      }
    }
  })
Vue.component('B',{
    data(){
      return {
        mymessage:this.message
      }
    },
    template:`
      <div>         // C组件能直接触发getCData,B组件调用C组件时,使用v-on绑定$listeners 属性
        <input type="text" v-model="mymessage" @input="passData(mymessage)">
        <C v-bind="$attrs" v-on="$listeners"></C>
      </div>       // 使用v-bind绑定$attrs属性,C可以获取到A传的props,不能获取B中props
    `,
    props:['message'],   // 得到父组件传递过来的数据
    methods:{
      passData(val){     // 触发父组件中的事件
        this.$emit('getChildData',val)
      }
    }
  })
Vue.component('A',{
    template:`
      <div>
        <p>this is parent compoent!</p>
        <B :messagec="messagec" :message="message" v-on:getCData="getCData" 
           v-on:getChildData="getChildData(message)"></B>
      </div>
    `,
    data(){
      return {
        message:'hello',
        messagec:'hello c'  // 传递给c组件的数据
      }
    },
    methods:{
      getChildData(val){
        console.log('这是来自B组件的数据')
      },
      getCData(val){        // 执行C子组件触发的事件
        console.log("这是来自C组件的数据:"+val)
      }
    }
  })

父组件向下传递数据(provide和inject)

provide(提供)和inject(注入)需要一起使用
允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效
祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量,解决跨级组件间的通信问题
主要使用场景是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系

Vue.component('parent',{
  template:`
    <div>
      <p>this is parent compoent!</p>
      <child></child>
    </div>
  `,
  provide:{
    for:'test'                  // 提供传递给子组件的数据
  },
  data(){
    return {
      message:'hello'
    }
  }
})
Vue.component('child',{
    inject:['for'],              // 得到父组件传递过来的数据
    data(){
      return {
        mymessage:this.for
      }
    },
    template:`
      <div>
        <input type="tet" v-model="mymessage">
      </div>
  })

得到组件实例,调用组件的方法访问数据 (parent/parent / children与 ref)

ref:如果在普通的DOM元素上使用,引用指向的就是DOM元素;如果用在子组件上,引用就指向组件实例
$parent 访问父实例
$children 访问子实例

export default {                 // component-a 子组件
  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;
      comA.sayHello();
    }
  }
</script>

总结

父子通信:父向子props,子向父$emit$parent/$children;provide/inject API;$attrs/$listeners
兄弟通信:eventBus;Vuex
跨级通信:eventBus;Vuex;provide/inject API;$attrs/$listeners