vue组件之间如何通信?vue通信的多种方式

@TOC

前言:

写在前面: vue已经更新到V2.6.10版本(相信很快就会出3.0版本),相信我们也遇到了需要组件之间通信的需求,除了主流的vuex状态管理模式,还有哪些方式解决组件之间的通信的问题,接下来就由我一一介绍给大家;

镇楼图

一、vuex状态管理模式

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

其数据流向如下:

数据流向

使用:

// cdn
<script src="/path/to/vue.js"></script>
<script src="/path/to/vuex.js"></script>

// npm 
npm install vuex --save

//yarn 
yarn add vuex

  • 使用
//  /src/store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
//  /src/store.js
/**
 * 状态树
 */
const state = {
  count: 0
}

/**
 * 和组件计算属性一样, store 的计算属性
 * getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算
 */
const getters = {
  getCount (state) {
    return state.count || 0
  }
}

/**
 * Vuex 中的 mutation 非常类似于事件:
 * 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
 * 每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)
 */
const mutations = {
  mutaCount (state, payload) {
    state.count = state.count + 1
  }

}
/**
 * Action 类似于 mutation,不同在于:
 * Action 提交的是 mutation,而不是直接变更状态。
 * Action 可以包含任意异步操作。
 */
const actions = {
  actCount ({commit}, payload) {
    commit('mutaCount', payload)
  }
}

const store = new Vuex.Store(
  {
    state,
    getters,
    mutations,
    actions
  }
)

export default store
// src/main.js
...
import store from './store'


/* eslint-disable no-new */
new Vue({
  ...
  store,
  ...
})

以上已经把vuex注入到vue实例;

组件中使用

// src/components/vuexOne.vue
//...
methods: {
    // ...mapActions(['actCount']), // 辅助函数方式使用,需要组件import {mapActions} from 'vuex'
    addCount () {
      // this.actCount()
      this.$store.dispatch('actCount')
    }
  }
//...

// src/components/vuexTwo.vue
//...
computed: {
    // ...mapGetters(['getCount']),  // 辅助函数方式使用,需要组件import {mapGetters} from 'vuex'
    // 常规方式使用
    getCount () {
      return this.$store.getters.getCount
    }
  }
//...

此时: 触发vuexOne.vue的addCount时间,vuexTwo.vue的页面能改更新;这就是vuex的简单使用;如需了解 模块module 及其他 辅助函数 等可以阅读文档vuex

效果如下:


vuex.gif

总结: Action和Mutation两者的功能很相似 ,并且很多时候,我们只需要在组件中通过this.$store.commit('xxx') 或者 mapMutations辅助函数来使用 Mutation 直接更新state的数据,而不需要通过 Action 这一步,但 ActionMutation 有个非常大的区别就是: Mutation 必须是同步函数(因为mutation 中混合异步调用会导致你的程序很难调试,所以在此限制为只能进行同步),而Action 可以包含任意异步操作

二、EventBus

EventBus 的实现原理是通过一个空的vue实例作为事件中心,通过它来触发事件($emit) 和监听事件($on), 巧妙而轻量地实现了任何组件间的通信; (适合少而小的项目使用,如果有大量通信,依旧推荐vuex)

使用:

首先在utils创建一个新的vue实例,用作 事件中心

// utils/eventbus.js
...
import Vue from 'vue'

export default new Vue({
  name: 'EventBus'
})

eventBusOne组件引入:

<template>
  <div class="eventBusOne">
    <div class="add-count-button-box">
      <div>我是eventBusOne组件:</div>
      <div class="add-count-button" @click="addCount">state++</div>
    </div>
  </div>
</template>
<script>
import eventBus from '../../../utils/eventBus'
export default {
  name: 'EventBusOne',
  data () {
    return {
      count: 1
    }
  },
  methods: {
    addCount () {
      this.count += 1
      eventBus.$emit('data-count', this.count)
    }
  }
}
</script>

eventBusTwo组件监听:

<template>
  <div class="eventBusTwo">
    <div class="add-count-button-box">
      <div>我是eventBusTwo组件:</div>
      <div>{{count}}</div>
    </div>

  </div>
</template>

<script>
import eventBus from '../../../utils/eventBus'
export default {
  name: 'EventBusTwo',
  data () {
    return {
      count: 1
    }
  },
  computed: {

  },
  mounted () {
    eventBus.$on('data-count', data => {
      this.count = data
    })
  }
}
</script>

效果如下:

eventBus.gif

总结: eventBus 原理 是利用一个空的vue实例当做一个事件中心,通过其分发及监听事件来传递数据,也可以实现任何组件间的通信,包括父子、兄弟、跨级等。但当使用过多容易造成命名冲突,因此不利于大项目使用(当大项目使用时,依旧推荐vuex)


ps:以上是目前使用比较多的可以跨组件包括兄弟组件通信的方法,接下来讲其他有短板的方法,有兴趣的可以花几分钟继续往下了解,否则客官可以止步于此,以免浪费您宝贵的时间 ...


三、使用最多之 props与$emit

props 由父组件A往子组件B传递数据,当然还可以继续组件B仍然可以往C组件(A的孙组件)继续往下传递,

使用 propsOne(父组件)

<template>
<div class="propsOne">
    <div class="add-count-button-box">
      <div>我是propsOne组件:</div>
      <div class="add-count-button" @click="addCount">count++</div>
      <div class="add-count-button" @click="addState">state++</div>
    </div>
  <propsTwo v-model="count" :state="state" @addCount="twoAddCount" @addState="twoAddState"></propsTwo>
</div>
</template>

<script>
import propsTwo from './propsTwo'
export default {
  name: 'PropsOne',
  components: {
    propsTwo
  },
  data () {
    return {
      count: 1,
      state: 1
    }
  },
  methods: {
    addCount () {
      this.count += 1
    },
    addState () {
      this.state += 1
    },
    twoAddCount (value) {
      this.count = value
    },
    twoAddState (value) {
      this.state = value
    }
  }
}

</script>

使用 propsTwo(子组件)

<template>
<div class="propsTwo">
  <div>count:{{value}}</div>
  <div class="state">state:{{state}}</div>
  <div>我是 propsTwo组件: </div>
  <div class="add-count-button" @click="addCount">count++</div>
  <div class="add-count-button" @click="addState">state++</div>
</div>
</template>

<script>
export default {
  name: 'PropsTwo',
  props: {
    value: {
      type: Number,
      default: 1
    },
    state: {
      type: Number,
      default: 1
    }
  },
  data () {
    return {
    }
  },
  methods: {
    addCount () {
      let count = this.value
      count++
      this.$emit('addCount', count)
    },
    addState () {
      let state = this.state
      state++
      this.$emit('addState', state)
    }
  }
}
</script>

效果如下:

props.gif

总结: props是单向数据流,即只能从父级传到子级,子级改变,父级的值不会改变(用.sync修饰符修饰可以实现双向数据绑定),但v-model是双向数据流,即双向绑定,子级改变这个值时,父级也会跟着改变;$emit传值和上面第二种的eventbus的原理一致,不过是事件分发到父级,父级可以监听;想了解sync修饰符请转vue.org

四、refs 、parent、$children

ref被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件

实例:

// ref.vue
<template>
<div class="ref">
 <div class="add-count-button" @click="getCount">
     获取refTwo的count,其值为:{{count}}
  </div>
  <refOne ref="refOne"></refOne>

</div>
</template>

<script>
import refOne from '../components/refDemo/refOne'
export default {
  name: 'Props',
  components: {
    refOne
  },
  data () {
    return {
      count: ''
    }
  },
  methods: {
    getCount () {
      console.log('ref========', this.$parent) // Vue
      console.log('ref========', this.$children) // refOne
      this.count = this.$refs.refOne.getCount()
    }
  }
}
</script>
// refOne
<template>
  <div class="refOne">
    <refTwo ref="refTwo"></refTwo>
    <refTwo></refTwo>
  </div>
</template>

<script>
import refTwo from './refTwo'
export default {
  name: 'RefOne',
  components: {
    refTwo
  },
  data () {
    return {
      count: 2
    }
  },
  methods: {
    getCount () {
      console.log('refOne========', this.$parent) // refOne
      console.log('refOne========', this.$children) // [refTwo,refTwo]
      return this.$refs.refTwo.getCount()
    }
  }
}
</script>
// refTwo
<template>
  <div class="refTwo">
    <div class="add-count-button-box">
      <div>我是refTwo组件的count ==== {{count}}</div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'RefTwo',
  data () {
    return {
      count: 1000
    }
  },
  methods: {
    getCount () {
      console.log('refTwo========', this.$parent) // refOne
      console.log('refTwo========', this.$children) // []
      return this.count
    }
  }
}
</script>

从上面的操作可知,通过ref调用子组件的方法,可以把相应的数据传导到父级;

特别地 $children拿到的当前实例的直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用 $children 来进行数据绑定,考虑使用一个数组配合 v-for 来生成子组件,并且使用 Array 作为真正的来源。

$parent、$children 从上述的打印,依旧可以发现$parent、$children能拿到当前组件的父级或者子级组件实例,如果有多个,则为数组,如果为空,则为空数组,如果通过这个实例去拿相应的属性或者方法也是可行的 如下:

// ref
methods: {
    getCount () {
      console.log('ref========', this.$parent) // Vue
      console.log('ref========', this.$children) // refOne
      this.count = this.$refs.refOne.count  // count 为 refOne的data里面的count ===2
    }
  }

ref请转vue.org
$parent请转vue.org
$children请转vue.org

其他

如: provide与inject

provide 和 inject (Vue2.2.0新增API) 绑定 并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。
provide与inject 转vue.org

如: $attrs/ $listeners

$attrs/ $listeners(Vue2.4增加) 版本在普通组件中,没有被定义为 prop 的特性会自动添加到组件的根元素上,将已有的同名特性进行替换或与其进行智能合并
$attrs/ $listeners转vue.org

总结:

万能通信: vuex、eventBus
父子通信:$refs 、 $parent、$children、provide/inject;

本文Demo请转 wLove-c
楼主博客请转 王一诺

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

推荐阅读更多精彩内容

  • vue是数据驱动视图更新的框架, 所以对于vue来说组件间的数据通信非常重要,那么组件之间如何进行数据通信的呢?首...
    云翼飞阅读 540评论 0 0
  • 原文地址 vue是数据驱动视图更新的框架, 所以对于vue来说组件间的数据通信非常重要,那么组件之间如何进行数据通...
    lovelydong阅读 390评论 0 0
  • 前言 组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引...
    用技术改变世界阅读 2,152评论 1 3
  • 摘要: 总有一款合适的通信方式。 作者:浪里行舟 Fundebug经授权转载,版权归原作者所有。 前言 组件是 v...
    Fundebug阅读 15,571评论 3 57
  • (摄于威尼斯) 回忆 是一条静静流淌的河 怀念堆积成 通向过往的桥 历经千回百转 终至彼岸 却发现 你抵达的不过是...
    米M妮阅读 943评论 12 43