【Vue组件之间的通信方式】


前言

组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。而很多情况下我们的组件之间是需要通信的,可以使用不同的方法(props/$emit,$emit/$on、vuex、$parent / $children/$ref、$attrs/$listeners和provide/inject)在不同关系(父子组件,如:A.Vue和B.Vue,隔代组件,如A.Vue和C.Vue,兄弟组件,如C.Vue和D.Vue)的组件之间进行通信。


一、props/$emit

父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息,如下图所示:
在这里插入图片描述

props:父组件向子组件传值

子组件在props中定义自己需要接收的参数,父组件通过v-bind:prop-name将参数传递给子组件
childB.Vue

<template>
    <div>
        <p>{{ title }}</p>
    </div>
</template>

<script>
    export default {
        name: 'child-B',
        props: {
            title: {        // 子组件B中定义title属性,接收父组件的传值
                type: String,
                default: ''
            }
        },
        data () {
            return {
            }
        }
    }
</script>

A.Vue

<template>
    <childB :title="childBTitle"></childB> <!-- 父组件将childBTitle与组件B的title属性进行绑定 -->
</template>

<script>
    import childB from '@/components/child_B'
    export default {
        components: {
            childB
        },
        data () {
            return {
                childBTitle: 'from A to B'
            }
        }
    }
</script>

在这里插入图片描述

二、$emit:父组件监听子组件的自定义事件

子组件可以通过提交自定义事件的方式,父组件监听该自定义事件,从而更新父组件的值
childB.Vue

<template>
    <div>
        <p>{{ title }}</p>
        <bk-button @click="changeATitle">ChildB_Button</bk-button>
    </div>
</template>

<script>
    export default {
        name: 'child-B',
        components: {
        },
        props: {
            title: {
                type: String,
                default: ''
            }
        },
        data () {
            return {
            }
        },
        methods: {
            changeATitle () { // 自定义事件titleChanged,传递值“子向父组件传值”
                this.$emit('titleChanged', '子向父组件传值')
            }
        }
    }
</script>

A.Vue

<template>
    <div>
        <childB :title="childBTitle" @titleChanged="updateTitle"></childB> // 监听titleChanged方法
        <h1>{{ title }}</h1>
    </div>
</template>

<script>
    import childB from '@/components/child_B'
    export default {
        components: {
            childB
        },
        data () {
            return {
                childBTitle: 'from A to B',
                title: ''
            }
        },
        methods: {
            updateTitle (e) { // e接收子组件传递的参数
                this.title = e
            }
        }
    }
</script>

在这里插入图片描述

双向绑定.sync

.sync修饰符是Vue 2.3.0+ 新增的,可用于父子组件之间值的“双向绑定”(以 update:myPropName 的模式触发事件代替真正的双向绑定),其实该方法就是props绑定父组件的值, e m i t 更新父组件值的简洁写法。子组件: ‘ t h i s . emit更新父组件值的简洁写法。 子组件:`this. emit更新父组件值的简洁写法。子组件:this.emit(‘update:title’, newTitle)`
父组件可以监听update方法

<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"></text-document>

然后使用.sync代替,可写成:
<text-document :title.sync="doc.title"></text-document>
另外,如果绑定的是一个Object,可以写成:
<text-document v-bind.sync="doc"></text-document>
这样会把 doc 对象中的每一个 property (如 title) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on 监听器。
B.Vue

<template>
    <div>
        <bk-button @click="changeATitle">ChildB_Button</bk-button>
        <p>ChildB:{{ name }}-{{ age }}-{{ weight }}</p>
    </div>
</template>

<script>
    export default {
        name: 'child-B',
        components: {
        },
        props: {
            name: {
                type: String,
                default: 'Bob'
            },
            age: {
                type: Number,
                default: 11
            },
            weight: {
                type: String,
                default: '50kg'
            }
        },
        data () {
            return {
            }
        },
        methods: {
            changeATitle () {
                this.$emit('update:name', 'Jack') // 自定义事件,传递值“Jack”
            }
        }
    }
</script>

A.Vue

<template>
    <div>
        <childB v-bind.sync="doc"></childB>
        <h1>ParentATitle:{{ doc.name }}-{{ doc.age }}-{{ doc.weight }}</h1>
    </div>
</template>

<script>
    import childB from '@/components/child_B'
    export default {
        components: {
            childB
        },
        data () {
            return {
                doc: {
                    name: 'Bob',
                    age: 11,
                    weight: '50kg'
                }
            }
        }
    }
</script>

$emit/$on

通过创建一个空的实例作为中央事件总线,我们可以借由它来监听和触发事件,这样就实现了任何组件之间的通信。
主要由以下三部分组成:
中央事件总线:var Event=new Vue()
事件触发组件:Event.$emit(事件名,数据)
事件监听组件:Event.$on(事件名,data => {})

创建中央事件总线

创建bus.js文件

import Vue from 'vue'

export const bus = new Vue() // 创建Vue实例并暴露出去

触发事件

A.Vue

<template>
    <div>
        <childB></childB>
        <bk-button @click="changeTitle">触发Bus事件</bk-button>
    </div>
</template>

<script>
    import { bus } from '@/common/bus'
    import childB from '@/components/child_B'
    export default {
        components: {
            childB
        },
        data () {
            return {
                doc: {
                    name: 'Bob',
                    age: 11,
                    weight: '50kg'
                }
            }
        },
        methods: {
            changeTitle () {
                bus.$emit('updateTitle', 'Jack') // 触发事件更新B的title
            }
        }
    }
</script>

监听事件

B.Vue

<template>
    <div>
        <p>{{ title }}</p>
    </div>
</template>

<script>
    import { bus } from '@/common/bus'
    export default {
        name: 'child-B',
        components: {
        },
        props: {
        },
        data () {
            return {
                title: 'test'
            }
        },
        created () {
            bus.$on('updateTitle', title => { // 监听事件,更新title
                this.title = title
            })
        }
    }
</script>

在这里插入图片描述

使用vue-happy-bus

为了使bus事件的自动销毁,我们可以引用vue-happy-bus插件
main.js

import BusFactory from 'vue-happy-bus'
Vue.prototype.$busFactory = BusFactory

A.Vue

data () {
    return {
        // bus实例
        Bus: this.$busFactory(this)
},
methods: {
    changeTitle () {
        this.Bus.$emit('updateTitle', 'Jack') // 触发事件更新B的title
    }
}

vuex

在这里插入图片描述

vuex介绍

vuex 是 vue 的状态管理器,存储的数据是响应式的。它由以下几个部分组成:

1)Vue Components:Vue组件。

2)dispatch:操作行为触发方法,是唯一能执行action的方法。

3)actions:操作行为处理模块,由组件中的$store.dispatch(‘action 名称’, data1)来触发。然后由commit()来触发mutation的调用 , 间接更新 state。

4)commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。

5)mutations:状态改变操作方法,由actions中的commit(‘mutation 名称’)来触发。

6)state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。

7)getters:state对象读取方法。

State用于保存数据,全局唯一,它可用于Vue组件的渲染,它的改变只能由Mutations来引起。组件可以通过调用dispatch方法,来触发Actions,Actions再把相应的改变提交到Mutations,从而引起State改变。

Vuex的使用

首先在src目录下创建如下目录,modules文件夹内可放置各个不同视图的一些全局参数,下面示例放置了一个侧边导航栏的收缩按钮参数。
在这里插入图片描述
B.Vue

子组件通过Getter获取属性值且可以通过dispatch触发Actions,再通过commit方法提交变化,从而引起全局参数的状态变化。
在这里插入图片描述

$parent/$children/$ref

ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
$parent / $children:访问父 / 子实例
需要注意的是:这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。但这种方式无法进行兄弟组件间的通信。
B.Vue

<template>
    <div>
        <p>{{ title }}</p>
    </div>
</template>

<script>
    export default {
        name: 'child-B',
        components: {
        },
        props: {
        },
        data () {
            return {
                title: 'test'
            }
        }
    }
</script>

A.Vue

<template>
    <div>
        <childB></childB>
        <bk-button @click="changeTitle">修改子组件的值</bk-button>
    </div>
</template>

<script>
    import childB from '@/components/child_B'
    export default {
        components: {
            childB
        },
        data () {
            return {
            }
        },
        methods: {
            changeTitle () {
                this.$children[0].title = 'ChangeBTitle'
            }
        }
    }
</script>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2s77s7Wk-1673320991030)(_v_images/20200929142041211_7448.png)]

$attrs/$listeners

对于嵌套的组件,即隔代组件之间的通信,我们可以使用 a t t r s / attrs/ attrs/listeners来进行属性的修改或者事件的监听。

  • a t t r s :包含了父作用域中不被 p r o p 所识别 ( 且获取 ) 的特性绑定 ( c l a s s 和 s t y l e 除外 ) 。当一个组件没有声明任何 p r o p 时,这里会包含所有父作用域的绑定 ( c l a s s 和 s t y l e 除外 ) ,并且可以通过 v − b i n d = " attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=" attrs:包含了父作用域中不被prop所识别(且获取)的特性绑定(classstyle除外)。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定(classstyle除外),并且可以通过vbind="attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。
  • l i s t e n e r s :包含了父作用域中的 ( 不含 . n a t i v e 修饰器的 ) v − o n 事件监听器。它可以通过 v − o n = " listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=" listeners:包含了父作用域中的(不含.native修饰器的)von事件监听器。它可以通过von="listeners" 传入内部组件
    A.Vue
<template>
    <div>
        <!-- 把属性传入,后代组件可以通过$attrs获取;监听自定义方法updateATitle -->
        <childB :foo="foo" :boo="boo" :coo="coo" :doo="doo" @updateATitle="updateTitle"></childB>
        <h>A标题:{{ title }}</h>
    </div>
</template>

<script>
    import childB from '@/components/child_B'
    export default {
        components: {
            childB
        },
        data () {
            return {
                foo: 'Javascript',
                boo: 'Html',
                coo: 'CSS',
                doo: 'Vue',
                title: 'Origin Title'
            }
        },
        methods: {
            updateTitle (e) {
                this.title = e
            }
        }
    }
</script>

B.Vue

<template>
    <div>
        <!-- 把$attrs传入,后代组件可以通过$attrs获取;$listeners将父组件的方法传入到后代组件 -->
        <grandsonC v-bind="$attrs" v-on="$listeners"></grandsonC>
        <p>B-attrs{{ $attrs }}</p>
    </div>
</template>

<script>
    import grandsonC from '@/components/grandson_C'
    export default {
        name: 'child-B',
        components: {
            grandsonC
        },
        props: {
            foo: {
                type: String,
                default: ''
            }
        },
        data () {
            return {
                title: 'test'
            }
        }
    }
</script>

C.Vue

<template>
    <div>
        <p>C-Attrs{{ $attrs }}</p>
        <bk-button @click="changTitle">孙子CA标题</bk-button>
    </div>
</template>

<script>
    export default {
        name: 'child-C',
        components: {
        },
        inheritAttrs: false,
        props: {
            bizInsts: {
                type: Array,
                default: []
            }
        },
        data () {
            return {
            }
        },
        computed: {
        },
        mounted () {
        },
        methods: {
            changTitle () {
                this.$emit('updateATitle', 'From C change') // 触发自定义事件
            }
        }
    }
</script>

在这里插入图片描述

provide/inject

Vue2.2.0新增API,这对选项需要一起使用。它可以用于隔代组件之间的传值,先代组件以provide属性传入参数,后代组件可以以inject属性去获取这些参数,不论层级有多深。
provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
A.Vue

<template>
    <div>
        <childB></childB>
    </div>
</template>

<script>
    import childB from '@/components/child_B'
    export default {
        components: {
            childB
        },
        provide: {
            title: '祖先标题'
        }
    }
</script>

B.Vue

<template>
    <div>
        <grandsonC></grandsonC>
        <p>B-Title{{ title }}</p>
    </div>
</template>

<script>
    import grandsonC from '@/components/grandson_C'
    export default {
        name: 'child-B',
        inject: ['title'],
        components: {
            grandsonC
        },
        props: {
        }
    }
</script>

C.Vue

<template>
    <div>
        <p>C-Title{{ title }}</p>
        <grandsonD v-bind="$attrs" v-on="$listeners"></grandsonD>
    </div>
</template>

<script>
    import grandsonD from '@/components/grandson_D'
    export default {
        name: 'child-C',
        inject: ['title'],
        components: {
            grandsonD
        }
    }
</script>

D.Vue

<template>
    <div>
        <p>D-Title{{ title }}</p>
    </div>
</template>

<script>
    export default {
        name: 'child-D',
        inject: ['title'],
        components: {
        }
    }
</script>

在这里插入图片描述
provide 和 inject 绑定并不是可响应的,即这种方式只能作为传值使用,如果要修改值的话,可以将祖先元素传入。


总结

组件同心的三种场景的适用方式如下:
父子组件通信:props/$emit$emit/$on(bus)vuex$parent/$children/$ref a t t r s / attrs/ attrs/listeners, provide/inject 【6】
嵌套多层组件通信:$emit/$on(bus)vuex, $attrs/$listeners, provide/inject 【4】
兄弟组件通信:$emit/$on(bus)vuex 【2】

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海夜风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值