Vue组件间通信7种方式

前言

vue的核心就是组件的使用,组件是可复用的vue实例。如果项目中某一个部分需要在多个页面中使用到,我们就可以将这部分代码抽成一个可复用的组件。
然而,组件实例的作用域之间是相互独立的,如果需要把组件之间的数据关联起来,这就需要懂组件之间的通信。

一、父组件向子组件传值(通过props)

父组件通过v-bind绑定变量,子组件通过props方式接收。
例子:在子组件Child.vue中如何获取到父组件App.vue中的数据title: '我是父组件的数据'
父组件

// App.vue 父组件

<template>
  <div id="app">
    <h2>父组件:</h2>
    <!-- :title 是传到子组件的变量名,便于子组件调用 -->
    <!-- title 是父组件中的data的属性值 -->
    <Child :title="title"/>
  </div>
</template>

<script>
import Child from './components/Child'

export default {
  name: 'App',
  data () {
    return {
      title: '我是父组件的数据'
    }
  },
  components: {
    Child
  }
}
</script>

<style>
</style>

子组件

// Child.vue 子组件

<template>
  <div class="child">
    <h3>子组件:{{title}}</h3>
  </div>
</template>

<script>
export default {
  name: 'Child',
  // 接收父组件的值
  props: {
    title: String
  }
}
</script>

<style scoped>
</style>

实现效果

父组件向子组件传值

总结:这种方式只能由父组件向子组件传递,子组件不能更新父组件内的data。也就是说,当父组件的属性发生变化时,将传递给子组件,但不会反过来,因为props是单向绑定的。

二、子组件向父组件传值(通过$emit)

相当于子组件调用父组件的方法。

子组件通过$emit发射一个方法,父组件通过v-on实现。
例子:当我们点击子组件的调用父组件中的方法按钮时,父组件中的内容我是父组件的内容修改成父组件的内容被修改了,从而实现子组件向父组件传值。
子组件

// Child.vue 子组件

<template>
  <div class="child">
    <h3>子组件:</h3>
    // 定义一个子组件传值的方法 handleClick
    <input type="button" value="调用父组件中的方法" @click="handleClick">
  </div>
</template>

<script>
export default {
  name: 'Child',
  data () {
    return {
      title: '父组件的内容被修改了'
    }
  },
  methods: {
    handleClick () {
      // titleChanged 自定义事件名
      // this.title 需要传给父组件的值
      this.$emit("titleChanged", this.title)
    }
  }
}
</script>

<style scoped>
</style>

父组件

// App.vue 父组件

<template>
  <div id="app">
    <h3>父组件 -- {{title}}</h3>
    <!-- titleChanged 与子组件中自定义事件名保持一致 -->
    <!-- updateTitle 方法名,需要接收子组件传递过来的值 -->
    <Child @titleChanged="updateTitle"/>
  </div>
</template>

<script>
import Child from './components/Child'

export default {
  name: 'App',
  data () {
    return {
      title: '我是父组件的内容'
    }
  },
  components: {
    Child
  },
  methods: {
    updateTitle (e) {
      // e 就是子组件传递过来的值
      this.title = e;
    }
  }
}
</script>

<style>
</style>

实现效果

子组件向父组件传值

三、父组件调用子组件的方法或访问数据(通过$ref调用)

例子:在父组件App.vue中访问子组件Child.vue中的title数据和调用childAlert方法。
子组件

// Child.vue

<template>
  <div class="child">
  </div>
</template>

<script>
export default {
  name: 'Child',
  data () {
    return {
      title: '我是子组件的内容'
    }
  },
  methods: {
    childAlert () {
      window.alert('我是子组件里面的弹窗!')
    }
  }
}
</script>

<style scoped>
</style>

父组件

// App.vue

<template>
  <div id="app">
    <Child ref="childRef"/>
  </div>
</template>

<script>
import Child from './components/Child'

export default {
  name: 'App',
  data () {
    return {
    }
  },
  components: {
    Child
  },
  mounted () {
    // 访问子组件的 title
    console.log(this.$refs.childRef.title)  // 输出‘我是子组件的内容’
    // 调用子组件的 childAlert 方法
    this.$refs.childRef.childAlert()
  }
}
</script>

<style>
</style>

总结:ref如果直接在普通的DOM元素上使用,引用所指向的就是DOM元素,如果在子组件上使用,引用所指向的就是组件实例
实现效果

父组件调用子组件的方法或访问数据

四、非父子组件之间的通信(中央事件总线)

该方法通过一个空的Vue实例作为中央事件总线,才能使用$emit获取$on的数据参数,实现组件通信。
创建一个空的Vue实例文件eventBus.js,也就是一个中央事件总线。

// eventBus.js

import Vue from 'vue'
export default new Vue()

创建A.vue组件,引入eventBus.js文件。

// A.vue 组件

<template>
  <div class="a_com">
    <h3>A组件:</h3>
    <input type="button" value="点击按钮给B组件传递数据" @click="emitBCom">
  </div>
</template>

<script>
// 引入空的 vue 实例
import eventBus from '../js/eventBus'

export default {
  name: 'A',
  data () {
    return {
      msg: '我是A组件的内容'
    }
  },
  methods: {
    emitBCom () {
      // bComHandle 自定义事件名,触发一个可以让B组件监听的方法
      // this.msg 要传给B组件的值
      eventBus.$emit('bComHandle', this.msg)
    }
  }
}
</script>

<style scoped>
</style>

创建B.vue组件,引入eventBus.js文件。

// B.vue 组件

<template>
  <div class="b_com">
    <h3>B组件:</h3>
    <p>接收A组件传递过来的值:{{msg}}</p>
  </div>
</template>

<script>
// 引入空的 vue 实例
import eventBus from '../js/eventBus'

export default {
  name: 'B',
  data () {
    return {
      msg: '',
    }
  },
  mounted () {
    // 监听A组件的自定义事件
    // data 这个data就是A组件传递过来的值
    eventBus.$on('bComHandle', data => this.msg = data)
  }
}
</script>

<style scoped>
</style>

App.vue中引入A.vueB.vue两个组件,并挂载到页面上。

// App.vue

<template>
  <div id="app">
    <a-com />
    <b-com />
  </div>
</template>

<script>
const ACom = () => import('./components/A')
const BCom = () => import('./components/B')

export default {
  name: 'App',
  components: {
    ACom,
    BCom
  }
}
</script>

<style>
</style>

整个过程的步骤:
新建一个空的Vue实例文件eventBus.js
在组件中引入定义的实例;
通过$emit触发一个自定义事件,并传递数据,eventBus.$emit(自定义事件名称,要传递的值)
把传递过来的自定义事件通过$on监听回调函数,eventBus.$on(自定义事件名称, () => {})

总结:这种只用一个Vue实例来作为中央事件总线来管理非父子组件通信的方法只适用于通信需求简单一点的项目,对于更加复杂的情况,需要使用Vue提供的状态管理模式Vuex来进行处理。
实现效果

非父子组件之间的通信

五、子组件调用父组件的方法或访问数据(另外一种方法:通过$parent)

例子:在子组件Child.vue中访问父组件App.vue中的msg数据和调用show方法。
父组件

// App.vue

<template>
  <div id="app">
    <child />
  </div>
</template>

<script>
const Child = () => import('./components/Child')

export default {
  name: 'App',
  data () {
    return {
      msg: '我是父组件中的内容'
    }
  },
  methods: {
    show () {
      console.log('我是父组件的方法!')
    }
  },
  components: {
    Child
  }
}
</script>

<style>
</style>

子组件

// Child.vue

<template>
  <div class="child">
    <h3>我是子组件:</h3>
    <p>访问父组件中的msg数据:{{msg}}</p>
  </div>
</template>

<script>
export default {
  name: 'Child',
  data () {
    return {
      msg: ''
    }
  },
  mounted () {
    // 访问父组件中的 msg 数据
    this.msg = this.$parent.msg
    // 调用父组件中的 show 方法
    this.$parent.show()
  }
}
</script>

<style scoped>
</style>

总结:用此方法前提得知道父组件是谁,如果项目中组件嵌套非常多的话,不推荐使用这个方法!
实现效果

子组件调用父组件的方法或访问数据

六、跨级组件间的通信(通过provide/inject)

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
provide选项应该是:一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。
inject选项应该是:一个字符串数组,或一个对象,对象的key是本地的绑定名,value是:在可用的注入内容中搜索用的key(字符串或Symbol),或一个对象,该对象的:from属性是在可用的注入内容中搜索用的 key(字符串或 Symbol),default属性是降级情况下使用的value
假设我们有两个组件Child.vueGrandparent.vue,来看一下比较简单的用法:
祖先级组件

// Grandparent.vue组件

<template>
  <div class="grandparent">
    <child />
  </div>
</template>

<script>
const Child = () => import('./Child')

export default {
  name: 'Grandparent',
  provide: {
    name: 'allen'
  },
  components: {
    Child
  }
}
</script>

<style scoped>
</style>

子孙级组件

// Child.vue

<template>
  <div class="child">
    <!-- 获取 Grandparent 组件中的 name 值 -->
    <h2>{{gp_name}}</h2>
  </div>
</template>

<script>

export default {
  name: 'Child',
  data () {
    return {
      gp_name: ''
    }
  },
  inject: ['name'],
  mounted () {
    this.gp_name = this.name
    console.log(this.name)      // 输出 allen
  }
}
</script>

<style scoped>
</style>

我们在祖先级组件中设置了一个provide:name,值为allen,它的作用就是将name这个变量提供给它的所有子孙级组件。而在子孙级组件中通过inject注入了从上级组件中提供的name变量,那么在子孙级组件中,就可以直接通过this.name来访问了。

提示:provideinject绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。

关于这对选项的深入用法,大家可以去看看官方文档哦!

七、兄弟组件之间的通信(通过Vuex)

Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
父组件App.vue

// App.vue

<template>
  <div id="app">
    <child-a />
    <child-b />
  </div>
</template>

<script>
const ChildA = () => import('./components/ChildA')  // 导入 ChildA 组件
const ChildB = () => import('./components/ChildB')  // 导入 ChildB 组件

export default {
  name: 'App',
  components: {
    ChildA,
    ChildB
  }
}
</script>

<style>
</style>

子组件ChildA.vue

// ChildA.vue

<template>
  <div class="child-a">
    <h3>A组件:</h3>
    <input type="button" value="点击按钮让B组件接收数据" @click="AComHandler">
    <p>B组件的数据:{{bmsg}}</p>
  </div>
</template>

<script>

export default {
  name: 'ChildA',
  data () {
    return {
      amsg: '我是A组件的内容'
    }
  },
  computed: {
    bmsg () {
      return this.$store.state.BComMsg
    }
  },
  methods: {
    AComHandler () {
      this.$store.commit('transferAComMsg', {
        AComMsg: this.amsg
      })
    }
  }
}
</script>

<style scoped>
</style>

子组件ChildB.vue

// ChildB.vue

<template>
  <div class="child-b">
    <h3>B组件:</h3>
    <input type="button" value="点击按钮让A组件接收到数据" @click="AComHandler">
    <p>A组件的数据:{{amsg}}</p>
  </div>
</template>

<script>

export default {
  name: 'ChildB',
  data () {
    return {
      bmsg: '我是B组件的内容'
    }
  },
  computed: {
    amsg () {
      return this.$store.state.AComMsg
    }
  },
  methods: {
    AComHandler () {
      this.$store.commit('transferBComMsg', {
        BComMsg: this.bmsg
      })
    }
  }
}
</script>

<style scoped>
</style>

引入vuex模块

> npm install vuex --save

src文件夹下创建store文件夹,并创建index.js文件

// index.js 

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    // 初始化A组件和B组件的数据
    AComMsg: '',
    BComMsg: ''
  },
  mutations: {
    // 将A组件数据存放到state中
    transferAComMsg (state, payload) {
      state.AComMsg = payload.AComMsg
    },
    // 将B组件数据存放到state中
    transferBComMsg (state, payload) {
      state.BComMsg = payload.BComMsg
    }
  }
})

export default store

main.js导入

// main.js

import Vue from 'vue'
import App from './App'

import store from './store/index'

Vue.config.productionTip = false

new Vue({
  el: '#app',
  store,
  render: h => h(App)
})

参考资料
Vue.js 官方文档
Vue.js API官方文档
Vue.js 组件通信方式
Vuex 官方文档使用
Vue进行兄弟组件通信

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349