Vue组件通信的几种方式【转】

Vue组件通信的几种方式【转】

组件通信主要有以下几种方式: props,$emit和$on,vuex,$attrs和$listeners,provide和inject,\$parent,$children与 ref,evenbus以及 $root 按使用场景可以划分为以下三类:

父子组件通信

props

$emit和$on

$parent和$children

ref

$attrs和$listeners

兄弟组件通信

$parent

$root

eventBus

vuex

跨层级通信

eventBus

provide和inject

vuex

1、props

这是父子间组件通信的常见方式,一般用于父组件向子组件传递数据,并且是响应式的。一般情况下子组件不会去直接改变从父组件同过 prop 传过来的数据,如果想改变的话,一般会在子组件 data 中定义一个变量来进行接收:

注意:这种通信方式是响应式的,一般情况下是用于单向通信的(父向子传递数据),但是如果在通过props特性传的是一个引用类型的数据(如 Object 和 Array )时,在子组件修改该引用类型的数据时,也会改变父组件中该props的值。所以这种方式也是可以在兄弟间通信的。如下面的例子,在child1或者child2中修改user 都会触发 user的更新。

//parent<template><child1:user="user"/><child2:user="user"/></template>importchild1from"./child1"importchild2from"./child2"exportdefault{data(){return{user:{userName:'james'}}},components:{child1,child2},}//child1.vue<template><span>{{userInfo.uaerName}}</span><button@click="change">修改</button></template>exportdeafult{props:{user:Object}data(){return{userInfo:this.user}},methods:{change(){this.userInfo.userName="username被修改了"}}}//child2.vue<template><span>{{userInfo.uaerName}}</span></template>exportdefault{props:{user:Object}data(){return{userInfo:this.user}}}

2、 \$emit和$on

这种通信方式主要是 解决子组件向父组件传递数据 的问题。不适合数据隔代传递(跨组件)。

//子组件中<button@click="send"></button>...methods:{send(){this.$emit("sendMsg","我是子组件的数据")}}//父组件中created(){this.$on("sendMsg",data=>{console.log(data);//"我是子组件的数据"})}

3、vuex

这种通信方式属于全局通信方式,一般vuex用在中大型项目中。 主要是有以下几个核心概念:

1、state: 用于保存整个项目中用到的全局变量,是响应式的。与之对应的是mapState函数(当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性)具体的用法可以参考官方文档。Vuex

2、getters: 可以认为是 store 的计算属性,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。 mapGetters: mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。

3、Mutation: 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。

注意: 1、Mutation 必须是同步函数;2、使用常量替代 Mutation 事件类型;3、Mutation 需遵守 Vue 的响应规则。

mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。

4、Action: Action 提交的是 mutation,而不是直接变更状态。 Action 可以包含任意异步操作。Action 通过 store.dispatch 方法触发。 在组件中分发 Action:this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store)。

5、Module Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。

4、$attrs和$listeners

attrs: 包含了父作用域中不被 props 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 props 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。 inheritAttrs为 true 时,表示在该组件上一html元素属性显示那些非 props 属性,为 false 则不显示。

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

//parent<template><div><child1:user="userInfo"class="test":userName="userInfo.userName":age='userInfo.age'></child1><button@click="change">调用子组件方法</button></div></template><script>importchild1from"./child1"exportdefault{name:"parent",data(){return{userInfo:{userName:'james',age:27}}},components:{child1},methods:{getUserName(){returnthis.userInfo.userName},change(){this.$children[0].change()}},}</script>//child1<template><div><span>{{userInfo.userName}}</span><button@click="change">修改</button><button@click="getUserInfo">调用父组件的方法</button></div></template><script>exportdefault{name:"child1",props:{user:Object},data(){return{userInfo:this.user}},created(){console.log(this.$attrs)//{age: 27,userName: "james"}console.log(this.$listeners)//可以直接调用this.$listeners.click()},methods:{change(){this.userInfo.userName="userName改变了"},getUserInfo(){constuserName=this.$parent.getUserName()console.log(userName)}}}</script>

5、provide和inject

provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。 子孙组件通过 inject 注入获取到祖级和父级 provide 的数据。

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

provide与inject 实现数据响应式的两种方式:

1、provide祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如 props,methods 2、使用2.6最新API Vue.observable 优化响应式 provide (推荐)

//parent.vue<template><div><child1:user="userInfo":userName="userInfo.userName"class="test":age='userInfo.age'></child1>{{userInfo}}</div></template><script>importchild1from"./child1"exportdefault{name:"parent",data(){return{userInfo:{userName:'james',age:27}}},provide(){return{user:this.userInfo}},watch:{'userInfo.userName':()=>{//在子组件中改变该属性,也会被监听到console.log("userName改变了")}},components:{child1},methods:{getUserName(){returnthis.userInfo.userName},},}</script>//child1.vue<template><div><span>{{user.userName}}</span><button@click="change">修改</button></div></template><script>exportdefault{name:"child1",inject:['user'],created(){console.log(this.user)},methods:{change(){this.user.userName="test"}}}</script>

6、$parent,$children与 ref

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

parent / children:访问父 / 子实例 这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。这两种方法的弊端是,无法在跨级或兄弟间通信。如果是封装通用组件时 parent / children 这种通信手段就显得不足了。

//parent<template><div><child1:user="userInfo"></child1><button@click="change">调用子组件方法</button></div></template><script>importchild1from"./child1"exportdefault{name:"parent",data(){return{userInfo:{userName:'james',}}},components:{child1},methods:{getUserName(){returnthis.userInfo.userName},change(){this.$children[0].change()}},}</script>//child<template><div><span>{{userInfo.userName}}</span><button@click="change">修改</button><button@click="getUserInfo">调用父组件的方法</button><!--<slotname='child1'childData="{test:'name'}"></slot> --></div></template><script>exportdefault{name:"child1",props:{user:Object},data(){return{userInfo:this.user}},methods:{change(){this.userInfo.userName="userName改变了"},getUserInfo(){//调用父组件方法constuserName=this.$parent.getUserName()console.log(userName)}}}</script>

element-ui在封装通用表单通用组件时,并没有在组件中直接通过 this.$parent、this.$children 来调用父/子组件的方法。为实现组件解耦,做了一层封装。具体封装封装代码如下。

exportdefault{methods:{dispatch(componentName,eventName,params){varparent=this.$parent||this.$root;varname=parent.$options.componentName;//父组件存在,父组件的componentName跟你传进来的componentName要一致while(parent&&(!name||name!==componentName)){parent=parent.$parent;if(parent){name=parent.$options.componentName;}}if(parent){parent.$emit.apply(parent,[eventName].concat(params));}},broadcast(componentName,eventName,params){this.$children.forEach(child=>{varname=child.$options.componentName;if(name===componentName){child.$emit.apply(child,[eventName].concat(params));}else{//调用自身broadcast.apply(child,[componentName,eventName].concat([params]));}});}}};

7、eventBus

这种通信方式可以跨组件通讯,经常用于兄弟组件间通讯。这种通讯方式的实现是通过新建一个 vue 实例,然后在需要通信的组件间引入,通过 emit 方法触发事件,通过 on 来监听相应事件来实现通讯的功能。更详细的请参考大神的介绍——vue组件间通信六种方式

//evenBus.jsimportVuefrom"vue"exportdefaultnewVue();

这是vue专题系列文章中的一篇。更多文章可以访问我的个人博客个人博客github地址。如果觉得总结的还可以,大家也可以fork我的框架。希望大家star支持一下。

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