8种方式实现vue.js 组件通信

组件系统

组件通讯是vue.js的核心之一,不管是在项目中,还是面试中,都是必考的知识点之一。组件通讯通常涉及到父子组件、兄弟组件、跨级组件的通讯。本文介绍组件间通信的几种常用方式,希望可以帮助大家更好的理解组件间的通信。

常用的几种组件通讯方式


  1. props/emit
  2. parent/children
  3. provide/inject
  4. ref
  5. eventBus
  6. vuex
  7. localStorage和sessionStorage
  8. $attr

一、props/emit

父组件通过props将变量传递给子组件,子组件通过$emit监听方法中传递参数,在父组件中监听子组件传递的方法获取到参数。

1、父组件向子组件传值
// 父组件
<template>
    <div>
        <children-com :title="title"></children-com>
    </div>
</template>
<script>
    import childrenCom from './childrenCom.vue';
    export default {
        components: {
            childrenCom
        },
        data() {
            return {
                title: '父组件传给子组件的值'
            }
        }
    }
</script>
// 子组件
<template>
    <div >
        <span>父组件传过来的值---{{title}}</span>
    </div>
</template>
<script>
    export default {
        props: {
            title: {
                type: String,
                default: ''
            }
        },
    }
</script>

props只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且props只读,不可被修改,所有修改会失效并警告。

2、子组件向父组件传值

$emit绑定一个事件,当执行时,将绑定的参数传递给父组件,父组件通过v-on监听并接收参数。在上面例子的基础上,点击button按钮,父组件显示子组件传递的值。

// 父组件
<template>
    <div>
        <children-com :title="title" @onEmitData="receptData"></children-com>
        <p>{{curVal}}</p>
    </div>
</template>
<script>
    import childrenCom from './childrenCom.vue';
    export default {
        components: {
            childrenCom
        },
        data() {
            return {
                title: '父组件传给子组件的值',
                curVal: ''
            }
        },
        methods: {
            receptData(args) {
                this.curVal = args;
            }
        }
    }
</script>
// 子组件
<template>
    <div >
        <span>父组件传过来的值---{{title}}</span>
        <Button @click="btnClick"></Button>
    </div>
</template>

<script>
    export default {
        props: {
            title: {
                type: String,
                default: ''
            }
        },
        methods: {
            btnClick() {
                this.$emit('onEmitData', '子组件给父组件传递的值')
            }
        }
    }
</script>

二、children/parent

vue官方解释,通过parent和children可以访问组件的实例,即意味着可访问此组件的所有方法和data数据。接下来通过案例来实现拿到指定组件的实例。

// 父组件
<template>
    <div>
        <children-com></children-com>
        <Button @click="changeChild">按钮</Button>
    </div>
</template>
<script>
    import childrenCom from './childrenCom.vue';
    export default {
        components: {
            childrenCom
        },
        data() {
            return {
                title: '父组件的值',
            }
        },
        methods: {
            changeChild() {
                this.$children[0].messageA = '修改子组件的值';
            }
        }
    }
</script>

// 子组件
<template>
    <div >
        <span>{{messageA}}</span>
        <p>获取父组件的值---{{parentVal}}</p>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                messageA: '子组件原有的值'
            }
        },
        computed: {
            parentVal() {
                return this.$parent.title;
            }
        }
    }
</script>

注意:如果在#app上拿parent得到的是new Vue()实例,在这个实例上再拿parent得到的是undefined,而在最底层的子组件拿children是个空数组。children的值是数组,而$parent是个对象

总结:上面两种方式皆用于父子组件之间的通信,而使用props进行父子通讯更加普遍,二者皆不可用于非父子组件之间的通信。

三、provide/inject

概念:
provide/inject是vue 2.2.0新增的api,主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。
这对选项需要一起使用,简单来说,就是祖先组件向子孙后代注入一个依赖,不论层级有多深。组件组件通过provide来提供变量,然后在子组件中通过inject来注入变量。

案例:有三个组件,A.vue,B.vue,C.vue,其中B是A的子组件,C是B的子组件

// 父组件 A.vue
<template>
    <div class="home">
      <grand-son></grand-son>
    </div>
</template>
<script>
import grandSon from '@/components/grandSon.vue'
export default {
    components: {
      grandSon
    },
    provide() {
      return {
        name: this.prop
      }
    },
    data() {
        return {
            prop: '父组件'
        }
    }
}
</script>


// 子组件 B.vue
<template>
    <div>
        <p class="grand-color">
            这是父组件传过来的----->{{name}}
        </p>
        <grandSonSon/>
    </div>
</template>
<script>
    import grandSonSon from './grandSonSon';
    export default {
        components: {
            grandSonSon
        },
        inject: ['name'],
        data() {
            return {

            }
        }
    }
</script>

// 孙子组件  C.vue
<template>
    <div>
        <p class="grand-color">
            这是祖先传过来的-----{{name}}
        </p>
    </div>
</template>
<script>
    export default {
        inject: ['name'],
        data() {
            return {

            }
        }
    }
</script>

四、ref/refs

ref:如果在普通的DOM元素上使用,引用指向的就是DOM元素;如果用在子组件上,引用就指向组件实例。可以通过实例直接调用组件的方法或访问数据。下面我们来看一个通过ref来访问组件的例子:

// 父组件
<template>
    <div>
        <children-com ref="comA"></children-com>
    </div>
</template>
<script>
    import childrenCom from './childrenCom.vue';
    export default {
        components: {
            childrenCom
        },
        mounted() {
            let comA = this.$refs.comA;
            console.log(comA.name);  // vue.js
            comA.say(); // hello
        }
    }
</script>

// 子组件 childrenCom .vue
export default {
    data() {
        return {
            name: 'vue.js'
        }
    },
    methods: {
        say() {
            console.log('hello');
        }
    }
}

五、eventBus

eventBus又称为事件总线,在vue中可以使用它来作为沟通桥梁的概念。就像是所有组件公用相同的事件中心,可以向该中心注册发送事件或接收事件,所有组件都可以通知其他组件。

eventBus也有不便之处,当项目较大时,就容器造成难以维护的灾难。

在项目中使用eventBus来实现组件之间的数据通信,需要以下几个步骤;

1、初始化

首先要创建一个事件总线并将其导出,以便其他模块都可以使用或监听它

// event-bus.js

import Vue from 'vue';
export default new Vue();
2、发送事件

有两个组件,这两个组件可以是兄弟组件也可以是父子组件;

<template>
    <div>
        <first />
        <second />
    </div>
</template>


<script>
    import first from '@/components/first';
    import second from '@/components/second'
    export default {
        components: {
            first,
            second
        }
    }
</script>

// 
<template>
    <div>
        <button @click="handleClick">加法器</button>
    </div>
</template>

<script>
    import bus from './event-bus';
    export default {
        data() {
            return {
                num: 1
            }
        },
        methods: {
            handleClick() {
                bus.$emit('handleBrother', {num: this.num++});
            }
        },
    }
</script>


3、接收事件
<template>
    <div>
        <span>计算和:{{count}}</span>
    </div>
</template>

<script>
    import bus from './event-bus.js';
    export default {
        data() {
            return {
                count: 0
            }
        },
        mounted() {
            bus.$on('handleBrother', (param) => {
                this.count += param.num
            })
        }
    }
</script>

这样就实现了在first.vue中点击相加按钮,在second.vue中利用传递的num展示求和的结果。

4、移除事件监听者
import bus from './event-bus.js';
bus.$off('handleBrother', {});

六、Vuex

1、Vuex介绍

Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex解决了==多个视图依赖于同一状态==。==来自不同视图的行为需要变更同一状态==的问题,将开发者的精力聚焦于数据的更新而不是数据在组件之间的传递上。

2、Vuex各个模块
  1. state:用于数据的存储,是store中的唯一数据源
  2. getters:如vue中的计算属性一样,基于state数据的二次包装,常用于数据的筛选和多个数据的相关性计算
  3. mutations:类似函数,改变state数据的唯一途径,且==不能用于处理异步事件==
  4. actions:类似于mutation,用于提交mutation来改变状态,而不是直接变更状态,可以包含任意异步操作
  5. modules:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护
3、Vuex实例

毫无关系的两个组件first.vue、second.vue,在first.vue中改变store.js中数据,在second组件同会同步更新;

// first.vue
<template>
    <div>
        <button @click="handleClick">改变store数据</button>
    </div>
</template>

<script>
    export default {
        methods: {
            handleClick() {
            this.$store.commit('changeMsgA', 'first组件改变的msgA数据');
            }
        }   
    }
</script>
// second.vue
<template>
    <div>
        <p>second.vue组件获取的store数据--{{$store.state.msgA}}</p>
    </div>
</template>

// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    msgA: ''
  },
  mutations: {
     changeMsgA(state, val) {
        state.msgA = val;
     }
  },
})

七、localStorage和sessionStorag

这两个方式是H5新增的用于本地存储,通信简单,但不容易维护。通过window.localStorage.getItem(key)获取数据,通过window.localStorage.setItem(key)存储数据

注意JSON.parse() / JSON.stringify()做数据格式转换

八、attr和listeners

隔代组件之间的通信方式,通常有以下几种:

  1. 使用props绑定一级一级的信息传递,如果孙子组件的状态改变需要传给祖先组件,需使用事件系统一级级往上传递
  2. 使用eventBus,碰到多人合作时,代码的维护性较低,代码可读性低;
  3. 使用VueX来进行数据管理,但是如果项目中多个组件共享状态比较少,项目比较小,并且全局状态比较少,那使用VueX来实现该功能,有点大材小用。
基本概念

vue2.4中,为了解决该需求,引入了attr和listeners。

inheritAttrs

默认情况下,子组件无法获取到props中未定义的值。通过将 inheritAttrs=true禁止这种默认行为,配合$attr获取到父作用域中所有的属性(除了props传递的属性以及Class或者Style)来进行父组件向子组件传值

vm.$attr

只读的VUE实例属性,包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外),并且可以通过v-binnd=$arrts传入内部组件中

vm.$listeners

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

接下来看一个跨级通信的例子:

// 父组件
<template>
    <div>
        <children :name="name" :age="age" :gender="gender" :height="height" @event1="goEvent1" @event2="goEvent2"></children>
    </div>
</template>
<script>
    import children from '@/components/children.vue'
    export default {
        components: {
            children
        },
        data() {
            return {
                name: 'zhang',
                age: '18',
                gender: '男',
                height: '189'
            }
        },
        methods: {
            goEvent1(val) {
                console.log(val);
            },
            goEvent2(value) {
                console.log(value);
            }
        }
    }

</script>

// children.vue
<template>
    <div>
        <p>name:{{name}}</p>
        <p>children的$attrs{{$attrs}}</p>
        <button type="button" @click="submit">提交</button>
        <grand-son v-bind="$attrs" v-on="$listeners"></grand-son>
        <!-- 通过$listeners将父作用域中的v-on事件监听器,传入grandSon,使得grandSon可以获取到app中的事件 -->
    </div>
</template>
<script>
    import grandSon from './grandSon';
    export default {
        props: {
            name: String
        },
        inheritAttrs: false,  // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
        components: {
            grandSon
        },
        methods: {
            submit() {
                this.$emit('event1', 'children提交事件');
            }
        }
    }

</script>

// grandSon.vue
<template>
    <div>
        <p class="grand-color">
            age:{{age}}
        </p>
        <p>childCom2--$attrs:{{$attrs}}</p>
        <input v-model="inputValue" name="" id="" @input="goInput">
    </div>
</template>
<script>
    export default {
        props: {
            age: String
        },
        data() {
            return {
                inputValue: ''
            }
        },
        methods: {
          goInput () {
            this.$emit('event2', this.inputValue);
          }
        }
    }
</script>

总结

组件通信常见使用场景可以分为三类:

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

推荐阅读更多精彩内容