Vue中组件间通信的方式

Vue中组件间通信包括父子组件、兄弟组件、跨级组件、非嵌套组件之间通信。

props $emit

props $emit适用于父子组件的通信,这种组件通信的方式是我们运用的非常多的一种,props以单向数据流的形式可以很好的完成父子组件的通信,所谓单向数据流,就是数据只能通过props由父组件流向子组件,而子组件并不能通过修改props传过来的数据修改父组件的相应状态,所有的props都使得其父子props之间形成了一个单向下行绑定,父级props的更新会向下流动到子组件中,但是反过来则不行,这样会防止从子组件意外改变父级组件的状态,导致难以理解数据的流向而提高了项目维护难度。实际上如果传入一个基本数据类型给子组件,在子组件中修改这个值的话Vue中会出现警告,如果对于子组件传入一个引用类型的对象的话,在子组件中修改是不会出现任何提示的,但这两种情况都属于改变了父子组件的单向数据流,是不符合可维护的设计方式的。
正因为这个特性,而我们会有需要更改父组件值的需求,就有了对应的$emit,当我们在组件上定义了自定义事件,事件就可以由vm.$emit触发,回调函数会接收所有传入事件触发函数的额外参数,$emit实际上就是是用来触发当前实例上的事件,对此我们可以在父组件自定义一个处理接受变化状态的逻辑,然后在子组件中如若相关的状态改变时,就触发父组件的逻辑处理事件。

父组件向子组件传值

父组件向子组件传值通过props传递值即可。

<!-- 子组件 -->
<template><div><div>我是子组件,接收:{{ msg }}</div></div>
</template><script>export default {name: "child",components: {},props: ["msg"],data: () => ({}),beforeCreate: function() {},created: function() {},filters: {},computed: {},methods: {}}
</script><style scoped></style>
<!-- 父组件 -->
<template><div><child :msg="msg"></child></div>
</template><script>import child from "./child";export default {components: { child },data: () => ({msg: "父组件 Msg"}),beforeCreate: function() {},created: function() {},filters: {},computed: {},methods: {}}
</script><style scoped></style>

子组件向父组件传值

子组件向父组件传值需要通过事件的触发,将更改值的行为传递到父组件去执行。

<!-- 子组件 -->
<template><div><div>我是子组件,接收:{{ msg }}</div><button @click="$emit('changeMsg', '子组件传值 Msg')">触发事件并传递值到父组件</button></div>
</template><script>export default {name: "child",components: {},props: ["msg"],data: () => ({}),beforeCreate: function() {},created: function() {},filters: {},computed: {},methods: {}}
</script><style scoped></style>
<!-- 父组件 -->
<template><div><child :msg="msg"@changeMsg="changeMsg"></child></div>
</template><script>import child from "./child";export default {components: { child },data: () => ({msg: "父组件 Msg"}),beforeCreate: function() {},created: function() {},filters: {},computed: {},methods: {changeMsg: function(msg){this.msg = msg;}}}
</script><style scoped></style>

v-model

v-model通常称为数据双向绑定,也可以称得上是一种父子组件间传值的方式,是当前组件与input等组件进行父子传值,其本质上就是一种语法糖,通过props以及input(默认情况下)的事件的event中携带的值完成,我们可以自行实现一个v-model

<template><div><div>{{msg}}</div><input :value="msg" @input="msg = $event.target.value"></div>
</template><script>export default {data: () => ({msg: "Msg"}),beforeCreate: function() {},created: function() {},filters: {},computed: {},methods: {}}
</script><style scoped></style>

sync修饰符

sync修饰符也可以称为一个语法糖,在Vue 2.3之后新的.sync修饰符所实现的已经不再像Vue 1.0那样是真正的双向绑定,而是和v-model类似,是一种语法糖的形式,也可以称为一种缩写的形式,在下面父组件两种写法是完全等同的。

<!-- 子组件 -->
<template><div><div>我是子组件,接收:{{ msg }}</div><button @click="$emit('update:msg', '子组件传值 Msg')">触发事件并传递值到父组件</button></div>
</template><script>export default {name: "child",components: {},props: ["msg"],data: () => ({}),beforeCreate: function() {},created: function() {},filters: {},computed: {},methods: {}}
</script><style scoped></style>
<!-- 父组件 -->
<template><div><child :msg="msg1"@update:msg="value => msg1 = value"></child><child:msg.sync="msg2"></child></div>
</template><script>import child from "./child";export default {components: { child },data: () => ({msg1: "父组件 Msg1",msg2: "父组件 Msg2",}),beforeCreate: function() {},created: function() {},filters: {},computed: {},methods: {changeMsg: function(msg){this.msg = msg;}}}
</script><style scoped></style>

provide inject

provide inject适用于父子组件以及跨级组件的通信,类似于ReactContext API,在父组件中通过provider来提供属性,然后在子组件中通过inject来注入变量,不论子组件有多深,只要调用了inject那么就可以注入在provider中提供的数据,而不是局限于只能从当前父组件的props属性来获取数据,只要在父组件内定义的provide的数据,子组件都可以调用。当然Vue中注明了provideinject主要在开发高阶插件/组件库时使用,并不推荐用于普通应用程序代码中。

<!-- 子组件 -->
<template><div><div>inject: {{msg}}</div></div>
</template><script>export default {name: "child",inject: ["msg"],data: () => ({}),beforeCreate: function() {},created: function() {},filters: {},computed: {},methods: {}}
</script><style scoped></style>
<template><div><child></child></div>
</template><script>import child from "./child";export default {components: { child },data: () => ({}),provide: {msg: "provide msg"},beforeCreate: function() {},created: function() {},filters: {},computed: {},methods: {}}
</script><style scoped></style>

$attrs $listeners

$attrs $listeners适用于直接的父子组件通信并通过props传递可以实现跨级组件的通信,假设此时我们有三个组件分别为ABC,父组件A下面有子组件B,父组件B下面有子组件C,这时如果组件A直接想传递数据给组件C那就不能直接传递了,只能是组件A通过props将数据传给组件B,然后组件B获取到组件A传递过来的数据后再通过props将数据传给组件C,当然这种方式是非常复杂的,无关组件中的逻辑业务增多了,代码维护也没变得困难,再加上如果嵌套的层级越多逻辑也复杂,无关代码越多,针对这样一个问题,Vue 2.4提供了$attrs$listeners来实现能够直接让组件A直接传递消息给组件C

<!-- 子子组件 -->
<template><div></div>
</template><script>export default {name: "child-child",components: {},data: () => ({}),beforeCreate: function() {},created: function() {console.log(this.$attrs); // {param: 1, test: 2}console.log(this.$listeners); // {testEvent: ƒ}},filters: {},computed: {},methods: {}}
</script><style scoped></style>
<!-- 子组件 -->
<template><div><!-- 直接将剩余的参数传递给子组件 --><child-child v-bind="$attrs" v-on="$listeners"></child-child></div>
</template><script>import childChild from "./child-child";export default {name: "child",components: { childChild },props: ["msg"], // 声明了接收名为msg的prop 此时在此组件的$attrs则不会再有msg参数data: () => ({}),inheritAttrs: false, // 默认设置为true也可 // 默认情况下true 父作用域的不被认作 props 的 attribute 绑定将会回退且作为普通的 HTML attribute 应用在子组件的根元素上。beforeCreate: function() {},created: function() {console.log(this.$attrs); // {param: 1, test: 2}console.log(this.$listeners); // {testEvent: ƒ}},filters: {},computed: {},methods: {}}
</script><style scoped></style>
<!-- 父组件 -->
<template><div><child :msg="msg":param="1":test="2"@testEvent="tips"></child></div>
</template><script>import child from "./child";export default {components: { child },data: () => ({msg: "Msg",}),beforeCreate: function() {},created: function() {},filters: {},computed: {},methods: {tips: function(...args){console.log(args);}}}
</script><style scoped></style>

$children $parent

$children $parent适用于父子组件以及跨级组件的通信,这种方式就比较直观了,直接操作父子组件的实例,$parent就是父组件的实例对象,而$children就是当前实例的直接子组件实例数组了,官方文档的说明是子实例可以用this.$parent访问父实例,子实例被推入父实例的$children数组中,节制地使用$parent$children它们的主要目的是作为访问组件的应急方法,更推荐用propsevents实现父子组件通信。此外在Vue2之后移除的$dispatch$broadcast也可以通过$children$parent进行实现,当然不推荐这样做,官方推荐的方式还是更多简明清晰的组件间通信和更好的状态管理方案如Vuex,实际上很多开源框架都还是自己实现了这种组件通信的方式,例如Mint UIElement UIiView等。

<!-- 子组件 -->
<template><div></div>
</template><script>export default {name: "child",data: () => ({}),beforeCreate: function() {},mounted: function() {console.log(this.$parent); // VueComponent {_uid: 2, ...}console.log(this.$children); // []},filters: {},computed: {},methods: {}}
</script><style scoped></style>
<!-- 父组件 -->
<template><div><child></child></div>
</template><script>import child from "./child";export default {components: { child },data: () => ({}),beforeCreate: function() {},mounted: function() {console.log(this.$parent); // VueComponent {_uid: 1, ...}console.log(this.$children); // [VueComponent]},filters: {},computed: {},methods: {}}
</script><style scoped></style>

$refs

$refs适用于父子组件通信,ref被用来给元素或子组件注册引用信息,引用信息将会注册在父组件的$refs对象上,如果在普通的DOM元素上使用,引用指向的就是DOM元素,如果用在子组件上,引用就指向组件实例。要注意的是因为ref本身是作为渲染结果被创建的,在初始渲染的时候是不能访问它们的,此时它们还不存在,另外$refs也不是响应式的,因此也不应该试图用它在模板中做数据绑定。

<!-- 子组件 -->
<template><div></div>
</template><script>export default {name: "child",data: () => ({}),beforeCreate: function() {},created: function() {},filters: {},computed: {},methods: {}}
</script><style scoped></style>
<!-- 父组件 -->
<template><div><child ref="child"></child></div>
</template><script>import child from "./child";export default {components: { child },data: () => ({}),beforeCreate: function() {},created: function() {},mounted: function(){console.log(this.$refs.child); // VueComponent {_uid: 3, ...}},filters: {},computed: {},methods: {}}
</script><style scoped></style>

EventBus

EventBus可以适用于任何情况的组件通信,在项目规模不大的情况下,完全可以使用中央事件总线EventBus 的方式,EventBus可以比较完美地解决包括父子组件、兄弟组件、隔代组件之间通信,实际上就是一个观察者模式,观察者模式建立了一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。所以发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。首先我们需要实现一个订阅发布类作为单例模块导出,并挂载到Vue.prototype作为Vue实例中可调用的全局对象使用,当然作为Mixins全局静态横切也可以,或者每个需要的组件再进行import也可以,此外务必注意在组件销毁的时候卸载订阅的事件调用,否则会造成内存泄漏。

// 实现一个PubSub模块
var PubSub = function() {this.handlers = {};
}PubSub.prototype = {constructor: PubSub,on: function(key, handler) { // 订阅if(!(key in this.handlers)) this.handlers[key] = [];if(!this.handlers[key].includes(handler)) {this.handlers[key].push(handler);return true;}return false;},once: function(key, handler) { // 一次性订阅if(!(key in this.handlers)) this.handlers[key] = [];if(this.handlers[key].includes(handler)) return false;const onceHandler = (...args) => {handler.apply(this, args);this.off(key, onceHandler);}this.handlers[key].push(onceHandler);return true;},off: function(key, handler) { // 卸载const index = this.handlers[key].findIndex(item => item === handler);if (index < 0) return false;if (this.handlers[key].length === 1) delete this.handlers[key];else this.handlers[key].splice(index, 1);return true;},commit: function(key, ...args) { // 触发if (!this.handlers[key]) return false;console.log(key, "Execute");this.handlers[key].forEach(handler => handler.apply(this, args));return true;},}export default new PubSub();
<!-- 子组件 -->
<template><div><div>{{msg}}</div><child></child></div>
</template><script>import child from "./child";export default {components: { child },data: () => ({msg: "init"}),beforeCreate: function() {},created: function() {this.eventBus.on("ChangeMsg", this.changeMsg);},beforeDestroy: function(){this.eventBus.off("ChangeMsg", this.changeMsg);},filters: {},computed: {},methods: {changeMsg: function(msg){this.msg = msg;}}}
</script><style scoped></style>
<!-- 父组件 -->
<template><div><div>{{msg}}</div><child></child></div>
</template><script>import child from "./child";export default {components: { child },data: () => ({msg: "init"}),beforeCreate: function() {},created: function() {this.eventBus.on("ChangeMsg", this.changeMsg);},beforeDestroy: function(){this.eventBus.off("ChangeMsg", this.changeMsg);},filters: {},computed: {},methods: {changeMsg: function(msg){this.msg = msg;}}}
</script><style scoped></style>

Vuex

Vuex同样可以适用于任何情况的组件通信,Vuex是一个专为Vue.js应用程序开发的状态管理模式,其采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
每一个Vuex应用的核心就是store仓库,store基本上就是一个容器,它包含着你的应用中大部分的状态stateVuex和单纯的全局对象有以下两点不同:

  • Vuex的状态存储是响应式的,当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  • 不能直接改变store中的状态,改变store中的状态的唯一途径就是显式地提交mutation,这样使得我们可以方便地跟踪每一个状态的变化。

实际上我们可以得到更多使用Vuex的优点:

  • 可以使用时间旅行功能。
  • Vuex专做态管理,由一个统一的方法去修改数据,全部的修改都是可以追溯的。
  • 在做日志搜集,埋点的时候,有Vuex更方便。
  • Vuex不会造成全局变量的污染,同时解决了父组件与孙组件,以及兄弟组件之间通信的问题。

当然如果项目足够小,使用Vuex可能是繁琐冗余的。如果应用够简单,最好不要使用Vuex,上文中的一个简单的store模式就足够了。
在下面例子中,我们通过提交mutation的方式,而非直接改变store.state.count,是因为我们想要更明确地追踪到状态的变化。这个简单的约定能够让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。此外这样也让我们有机会去实现一些能记录每次状态改变,保存状态快照的调试工具。

import Vue from "vue";
import Vuex from "vuex";Vue.use(Vuex);const store = new Vuex.Store({state: {count: 0},mutations: {increment: function(state) {state.count++;}}
})store.commit("increment");
console.log(store.state.count); // 1

由于store中的状态是响应式的,在组件中调用store中的状态简单到仅需要在计算属性中返回即可。触发变化也仅仅是在组件的methods中提交mutation即可。

import Vue from "vue";
import Vuex from "vuex";Vue.use(Vuex);const store = new Vuex.Store({state: {count: 0},mutations: {increment: state => state.count++,decrement: state => state.count--}
})new Vue({el: "#app",store,  computed: {count: function() {return this.$store.state.count;}},methods: {increment: function() {this.$store.commit("increment");},decrement: function() {this.$store.commit("decrement");}}
})

每日一题

https://github.com/WindrunnerMax/EveryDay

参考

https://zhuanlan.zhihu.com/p/109700915
https://juejin.cn/post/6844903887162310669
https://juejin.cn/post/6844903784963899405
https://segmentfault.com/a/1190000022083517
https://github.com/yangtao2o/learn/issues/97
https://github.com/YangYmimi/read-vue/issues/12

Vue中组件间通信的方式相关推荐

  1. React中组件间通信的方式

    React中组件间通信的方式 React中组件间通信包括父子组件.兄弟组件.隔代组件.非嵌套组件之间通信. Props props适用于父子组件的通信,props以单向数据流的形式可以很好的完成父子组 ...

  2. vue中组件间通信的6种方式

    前言 组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互进行直接的引用,所以组件间的相互通信是非常重要的. 除了使用vuex外还有下面6种组件间 ...

  3. vue组件间通信六种方式

    vue组件间通信六种方式 组件之间的传值通信 组件之间的通讯分为三种:父传子.子传父.兄弟之间的通讯: props/$emit 父传子:主要是通过props来实现的 具体实现:父组件通过import引 ...

  4. vue组件间通信六种方式(完整版)

    前言   组件是 vue.js 最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用.一般来说,组件可以有以下几种关系:                如上图所示 ...

  5. 【Vue父子组件间通信】

    Vue父子组件间通信 父子组件通信 父传子 (props) 子传父 (自定义事件) 完整代码 效果 父子组件通信 父传子 (props) (1)在父组件中使用子组件时,给子组件一个自定义属性,这个属性 ...

  6. vue组件穿方法_vue组件间通信六种方式(完整版)

    [51CTO.com原创稿件] 前言 组件是 vue.js强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用.一般来说,组件可以有以下几种关系: 如上图所示,A ...

  7. Vue实现组件间通信的七种方式

    1. props / $emit 父组件通过props的方式向子组件传递数据,而通过$emit 子组件可以向父组件通信: 父传子:父组件通过import引入子组件,并注册,在子组件标签上添加要传递的属 ...

  8. Vue 组件间通信六种方式

    前言 组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用.一般来说,组件可以有以下几种关系: 如上图所示,A 和 B.B 和 C.B 和 D ...

  9. Vue(组件间通信:props、自定义事件、全局事件总线、消息订阅与发布)

    一.props props不仅可以实现父给子传递信息,还可以进行子给父传递信息 1.父给子传递信息: 父组件中给子组件实例传递信息 子组件利用props进行接收组件传递信息(接收方式有三种:数组.对象 ...

最新文章

  1. 苹果笔记本只有电源键能用的解决办法
  2. Excel技巧之——英文大小写转换(转)
  3. python面试-10个Python面试常问的问题(小结)
  4. 机房USB被禁用的方法
  5. C# DataSet性能最佳实践
  6. 2020双11,阿里巴巴集团数万数据库系统全面上云揭秘
  7. 论文写作思路_2018年的16个写作思路
  8. 使用 show status 命令
  9. linux c++ 函数效率,C++高精度性能测试函数
  10. 微信小程序报Cannot read property ‘setData‘ of undefined的错误
  11. UILabel的相关属性设置
  12. FILEUTILS 介绍
  13. 什么是CSS+DIV及其优势所在?
  14. vm虚拟机 centos7 联网(设置静态ip)
  15. LabVIEW安装多个NI软件产品时的安装顺序
  16. 拨号时显示无法连接服务器失败,拨号网络常见的错误提示解释及解决方法大全...
  17. Unity3d bounds包围盒 和collider碰撞器区别
  18. A Native Collection has not been disposed, resulting in a memory leak. Enable Full StackTraces to ge
  19. 关于“ROS2 Topic-Statistics-Tutorial编译出错”的思考2
  20. vue中禁止ios橡皮筋效果(亲测有效)

热门文章

  1. 源码分析 | 深度解密Go语言之context
  2. Redis:Hot Key问题
  3. 通俗易懂的vuex-demo
  4. 将一个二维数组合并成一个一维数组
  5. 一个想法(续四):IT技术联盟创业众筹进度公示
  6. OAuth2.0学习(1-7)授权方式4-客户端模式(Client Credentials Grant)
  7. 写在弥勒宝贝两周年之际
  8. 常见端口、端口查询及TCP状态
  9. php 0和字符串比较为真
  10. UESTC 2014 Summer Training #19