超实用的 Vue 组件通信方式大汇总(8种)

前言

组件(Component)是 Vue 最核心的功能,也是整个框架设计最出彩的地方,而组件实例的作用域是相互独立的,也就是说不同组件间的数据是无法直接互相引用的。那么,组件之间是如何进行通信传递数据的呢?这就需要我们先搞清楚组件之间的关系:

一、Vue组件关系

vue组件通信关系图
如上图所示,组件间的关系有:

  • 父子关系:A与B、A与C、B与D、C与E
  • 兄弟关系:B与C
  • 隔代关系(可能隔多代):A与D、A与E
  • 非直系亲属关系:D与E

以上关系可总结为三大类:父子组件通信、兄弟组件通信、跨级组件通信。

二、Vue组件通信方式

Vue组件的通信方式,总结起来有以下8种:

1. props / $emit

1.1 props 父传子

父组件向子组件传递数据或参数,是通过 props 来实现的。在父组件中在自定义的子组件标签上添加要传递的 props 的名称及数据(可通过v-bind动态绑定props的值),而子组件通过选项 props 来声明需要从父组件接收的数据,props 的值分为两种:一种是字符串数组,一种是对象(当prop需要验证时使用,推荐)。

<!--parent.vue-->
<template>
    <div>
        <!--props的值可以直接传递也可以动态绑定-->
        <child message1="我是父组件直接传递的数据" :message2="message"></child>
    </div>
</template>

<script>
    import child from "./child"
    export default {
        name: 'parent',
        components: {
            child
        },
        data() {
            return {
                message: ['我是父组件动态绑定传递的数据1', '我是父组件动态绑定传递的数据2', '我是父组件动态绑定传递的数据3']
            }
        }
    }
</script>
 <!--child.vue-->
<template>
    <div>
        <p>{{message1}}</p>
        <ul>
            <li v-for="(item,index) in message2" :key=index>{{item}}</li>
        </ul>
    </div>
</template>

<script>
    export default {
        name: 'child',
        //props: ['message1', 'message2'], //props的第一种:字符串数组
        props: {//props的第二种:对象
            message1: {
                type: String,
                default: ''
            },
            message2: {
                type: Array,
                default: () => []
            }
        },
        data() {
            return {

            }
        }
    }
</script>
1.2 $emit 子传父

通过props传递数据是单向的,父组件数据变化时会传递给子组件,但子组件不能通过修改props传过来的数据来修改父组件的相应状态,即所谓的单向数据流。因为这种特性,子组件需要向父组件传递数据时,就要用到自定义事件。子组件用 $emit() 来出发事件,父组件用 $on() 来监听子组件的事件,也可以直接在子组件的自定义标签上使用v-on来监听子组件触发的自定义事件。

<!--parent.vue-->
<template>
    <div>
        <p>{{parentData}}</p>
        <!--在子组件自定义标签上用v-on(此处用的语法糖@)监听子组件触发的自定义事件clickEvent-->
        <child @clickEvent='parentClickEvent'></child>
    </div>
</template>

<script>
    import child from "./child"
    export default {
        name: 'parent',
        components: {
            child
        },
        data() {
            return {
                parentData: '父组件本来的数据'
            }
        },
        methods: {
            parentClickEvent(val) {
                this.parentData = val; //val是子组件传递过来的数据
            }
        }
    }
</script>
<!--child.vue-->
<template>
    <button @click="childClickEvent">点击修改父组件数据</button>
</template>

<script>
    export default {
        name: 'child',
        data() {
            return {

            }
        },
        methods: {
            childClickEvent() {
                //$emit方法第一个参数是自定义事件(clickEvent),后面的参数都是要传递的数据,可以不填或填写多个
                this.$emit('clickEvent', '子组件向父组件传递的数据');
            }
        }
    }
</script>
1.3 父子组件双向绑定
1.3.1 v-model

Vue 2.X 可以在自定义组件上使用 v-model 指令,实现父子组件之间的通信,该方式与上面介绍的方式类似,是一个语法糖。父组件通过 v-model 向子组件传递数据时,会自动传递一个 valueprop 属性,而子组件通过 this.$emit('input',val) 自动修改 v-model 绑定的值。

<!--parent.vue-->
<template>
    <div>
        <h2>我是父组件</h2>
        <p>我是父组件的数据:{{pData}}</p>
        <child v-model="pData"></child>
    </div>
</template>
<script>
    import child from './child'
    export default {
        name: 'parent',
        components: {
            child
        },
        data() {
            return {
                pData: '我是父组件的数据'
            }
        }
    }
</script>
<!--child.vue-->
<template>
    <div>
        <h2>我是子组件</h2>
        <p>我是子组件的数据: {{cData}}</p>
        <p>我是父组件传递过来的数据: {{msg}}</p>
        <button @click="handleClick">点击传递子组件数据给父组件</button>
    </div>
</template>
<script>
    export default {
        name: 'child',
        props: ['value'],//v-model 会自动传递一个字段为 value 的 props 属性
        data() {
            return {
                msg: this.value,
                cData: '我是子组件的数据'
            }
        },
        methods: {
            handleClick() {
                this.$emit('input', this.cData);//通过emit特定的input事件可以改变父组件上v-model绑定的值
            }
        }
    }
</script>

在这里插入图片描述

1.3.2 .sync

某些情况下,我们需要实现某个 prop 的双向绑定,而真正的双向绑定会带来维护上的问题,因为子组件可以修改父组件,且在父子组件中都没有明显的改动来源,所以官方推荐以 update:my-prop-name 的模式触发事件实现 “上行绑定“” 最终实现 “双向绑定“。而 .sync 是一个编译时的语法糖,它会被自动扩展为一个自动更新父组件属性的 v-on 监听器。如:<child :abc.sync=”msg”></child> 就会被扩展为: <child :abc=”data” @update:abc=”val => data= val”>@v-on 的简写)。当子组件需要更新 abc 的值的时候,他需要显示的触发一个更新事件:this.$emit( “update:abc”, newValue )。当使用一个对象一次性设置多个属性的时候,这个 .sync 修饰符也可以和 v-bind 一起使用。如:<child v-bind.sync = “{ a: data1, b: data2}”></child> (不能写成 :.sync=...,否则会报错),这样会为 ab 同时添加用于更新的 v-on 监听器。

<!--parent.vue-->
<template>
    <div>
        <h2>我是父组件</h2>
        <p>我是父组件的数据(单属性){{pData}}</p>
        <p>我是父组件的数据(多属性){{myProps.a1}},{{myProps.a2}}</p>
        <!--1.单个属性传递-->
        <child :abc.sync="pData" :ifShow="true"></child>
        <!-- <child :abc="pData" @update:abc="val=>pData= val"></child> 上面会自动扩展为该形式-->
        <!--2.多个属性传递-->
        <child v-bind.sync="myProps" :ifShow="false"></child> <!-- 不能写成字面量形式如 v-bind.sync="{ a1: '我是父组件的pData1', a2: '我是父组件的pData2'}"-->
    </div>
</template>
<script>
    import child from './child'
    export default {
        name: 'parent',
        components: {
            child
        },
        data() {
            return {
                pData: 'Hi!我是父组件!',
                myProps: { a1: '我是父组件的pData1', a2: '我是父组件的pData2' }
            }
        }
    }
</script>
<!--child.vue-->
<template>
    <div>
        <h2>我是子组件</h2>
        <p v-if="ifShow">我是子组件接收到的父组件单个属性:{{abc}}</p>
        <p v-else>我是子组件接收到的父组件的多个属性:{{a1}},{{a2}}</p>
        <button @click="handleClick">点击传递子组件数据给父组件</button>
    </div>
</template>
<script>
    export default {
        name: 'child',
        props: ['ifShow', 'abc', 'a1', 'a2'],
        data() {
            return {
                cData: 'Hi!我是子组件!'
            }
        },
        methods: {
            handleClick() {
                this.$emit("update:abc", this.cData);
                this.$emit("update:a1", this.cData);
                this.$emit("update:a2", this.cData);
            }
        }
    }
</script>

如果你只是单纯的在子组件中修改父组件的某个数据,建议使用.sync

注意:将 v-bind.sync 用在一个字面量的对象上,例如v-bind.sync=”{ title: doc.title
}”,是无法正常工作的,因为在解析一个像这样的复杂表达式的时候,有很多边缘情况需要考虑。

v-model.sync 都可以实现 props 的双向绑定,但是 v-model 有局限性,只能传递 value 属性,而 .sync 可以传递其他的属性值。

2. $parent / $children

2.1 $parent / $children

子组件使用 $parent 可以直接访问父组件的实例(对象),而父组件通过 $children 可以访问所有的子组件实例(数组),并且可以递归向上或向下无限访问,直到根实例或最内层组件。虽然 Vue 允许这样操作,但在实际处理中,不建议这么做,因为这样会使父子组件紧耦合,而且会使父组件的状态因为可能被任意组件修改而难以理解。

<!--parent.vue-->
<template>
    <div>
        <child></child>
        <button @click="parentClickEvent">点击修改子组件数据</button>
    </div>
</template>

<script>
    import child from "./child"
    export default {
        name: 'parent',
        components: {
            child
        },
        data() {
            return {
                parentData: '父组件的数据'
            }
        },
        methods: {
            parentClickEvent() {
                this.$children[0].childData = '这是父组件修改的子组件数据';
            }
        }
    }
</script>
<!--child.vue-->
<template>
    <div>
        <p>子组件数据:{{childData}}</p>
        <p>子组件获取的父组件数据:{{parentData}}</p>
    </div>
</template>

<script>
    export default {
        name: 'child',
        data() {
            return {
                childData: '子组件的数据'
            }
        },
        computed: {
            parentData() {
                return this.$parent.parentData;
            }
        }
    }
</script>
2.2 $dispatch / $broadcast

这也是一组成对出现的方法,在 Vue2.0 中被废弃了,但是还是有很多开源软件自己封装了这种组件通信方式,如 Mint UI、Element UI 和 iView 等,可以解决父子组件、嵌套父子组件的通信。核心是向上寻找 $parent 和遍历 $children,使用 $on$emit 进行事件的监听和调用。通过 $dispatch$broadcast 定向的向某个父或者子组件远程调用事件,这样就避免了通过传 props 或者使用 refs 调用组件实例方法的操作。

//main.js
import Vue from 'vue'
import parent from './parent'

//在Vue的原型上添加$dispatch方法,通过this.$dispatch调用
Vue.prototype.$dispatch = function (eventName, params) {
    let parent = this.$parent;
    while (parent) {
        parent.$emit(eventName, params);
        parent = parent.$parent;
    }
};

//在Vue的原型上添加$broadcast方法,通过this.$broadcast调用
Vue.prototype.$broadcast = function (eventName, params) {
    //获取当前组件下所有的子孙组件,递归调用
    const boradcast = children => {
        children.forEach(child => {
            child.$emit(eventName, params);
            if (child.$children) {
                boradcast(child.$children);
            }
        });
    }
    boradcast(this.$children);
};

new Vue({
    render: h => h(parent)
}).$mount("#app")
<!--parent.vue-->
<template>
    <div>
        <h2>我是parent.vue</h2>
        <p>parent组件:{{pData}}</p>
        <button @click="clickEvent">所有子孙组件(-100)</button>
        <child></child>
    </div>
</template>
<script>
    import child from './child'
    export default {
        name: 'parent',
        components: {
            child
        },
        data() {
            return {
                pData: 100
            }
        },
        methods: {
            test(val) {
                this.pData += val;
            },
            clickEvent() {
                this.$broadcast('broadcastEvent', -100);//向所有子孙广播
            }
        },
        mounted() {
            this.$on('dispatchEvent', this.test);//用$on监听
        }
    }
</script>
<!--child.vue-->
<template>
    <div>
        <h2>我是child.vue</h2>
        <p>child组件:{{cData}}</p>
        <grandson></grandson>
    </div>
</template>
<script>
    import grandson from './grandson'
    export default {
        name: 'child',
        components: {
            grandson
        },
        data() {
            return {
                cData: 200
            }
        },
        methods: {
            test(val) {
                this.cData += val;
            }
        },
        mounted() {
            this.$on('dispatchEvent', this.test);//用$on监听dispatchEvent
            this.$on('broadcastEvent', this.test);//用$on监听broadcastEvent
        },
    }
</script>
<!--grandson.vue-->
<template>
    <div>
        <h2>我是grandson.vue</h2>
        <p>grandson组件:{{gData}}</p>
        <button @click="clickEvent">所有祖先组件(+100)</button>
    </div>
</template>
<script>
    export default {
        name: 'grandson',
        data() {
            return {
                gData: 100
            }
        },
        methods: {
            test(val) {
                this.gData += val;
            },
            clickEvent() {
                this.$dispatch('dispatchEvent', 100);//向所有的祖先派发
            }
        },
        mounted() {
            this.$on('broadcastEvent', this.test);//用$on监听broadcastEvent
        }
    }
</script>

在这里插入图片描述

3. $attrs / $listeners

对于隔代关系,如上图中父组件A和孙组件D之间想要传递数据,按照上面的方法,只能是在组件A,组件B,组件D这个链条中一级级使用 props$emit向下和向上进行数据传递,如果中间有更多的层级,这种方式就更加复杂难以维护。为此,Vue2.4 版本提供了 $attrs$listeners 来解决这种跨级通信的需求:

  • $attrs:包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。
  • $listeners :包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件。
<!--parent.vue-->
<template>
    <div>
        <child :a1="a1" :a2="a2" :a3="a3" :a4="a4" @click.native="clickEventNative" @click="clickEvent"
            @pEvent1="parentEvent1" @pEvent2="parentEvent2">
        </child>
    </div>
</template>

<script>
    import child from "./child"
    export default {
        name: 'parent',
        components: {
            child
        },
        data() {
            return {
                a1: "我是parent属性1的数据",
                a2: "我是parent属性2的数据",
                a3: "我是parent属性3的数据",
                a4: "我是parent属性4的数据",
            }
        },
        methods: {
            clickEventNative() {
                console.log('我是parent的native事件');
            },
            clickEvent() {
                console.log('我是parent事件0');
            },
            parentEvent1() {
                console.log('我是parent事件1');
            },
            parentEvent2() {
                console.log('我是parent事件2');
            }
        }
    }
</script>
<!--child.vue-->
<template>
    <div>
        <p>child中接收到的a1: {{a1}}</p>
        <p>child中接收到的$attrs: {{$attrs}}</p>
        <grandson v-bind="$attrs" v-on="$listeners"></grandson>
    </div>
</template>

<script>
    import grandson from './grandson'
    export default {
        name: 'child',
        components: {
            grandson
        },
        props: ['a1'],
        mounted() {
            console.log(this.$listeners);//{click: ƒ, pEvent1: ƒ, pEvent2: ƒ}
            this.$emit('pEvent1');//parent方法调用方式1
        },
    }
</script>
<!--grandson.vue-->
<template>
    <div>
        <p>grandson中接收到的a2: {{a2}}</p>
        <p>grandson中接收到的a3: {{a3}}</p>
        <p>grandson中接收到的$attrs: {{$attrs}}</p>
    </div>
</template>

<script>
    import grandSon from './grandson'
    export default {
        name: 'child',
        inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
        props: ['a2', 'a3'],
        mounted() {
            this.$listeners.pEvent2();//parent方法调用方式2
        },
    }
</script>

运行结果:
在这里插入图片描述
例子中,给 grandson.vue 加上 inheritAttrs:false 属性前后如图所示:
在这里插入图片描述
在这里插入图片描述

4. provide / inject

4.1 provide / inject 使用方法

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。即,在父组件中通过 provider 来提供属性,然后在子组件中通过 inject 来注入变量。不论子组件有多深,只要调用了 inject 那么就可以注入在 provider 中提供的数据,只要在父组件的生命周期内,子组件都可以调用。

<!--parent.vue-->
<template>
    <div>
        <child></child>
    </div>
</template>

<script>
    import child from "./child"
    export default {
        name: 'parent',
        components: {
            child
        },
        provide: {//provide选项可以是一个对象或返回一个对象的函数
            parentData: '我是父组件的数据'
        },
    }
</script>
<!--child.vue-->
<template>
    <div>{{parentData}}</div>
</template>

<script>
    export default {
        name: 'child',
        inject: ['parentData']//injec选项可以是一个字符串数组或一个对象
    }
</script>
4.2 实现 provide / inject 数据响应式

provide 和 inject 绑定并不是可响应的,即修改了上例中 parent.vue 中的parentData,child.vue 中的 parentData 是不会改变的。要实现数据响应式,有两种方法:

  1. provide 祖先组件的实例,在后代组件中注入依赖。这样就可以在后代组件中直接修改祖先组件实例的属性。
  2. 使用 Vue. observable 优化响应式 provide(2.6新增API,推荐)
<!--parent.vue-->
<template>
    <div>
        <p>{{pData}}</p>
        <child></child>
        <button @click=changeData>改变parentData</button>
    </div>
</template>

<script>
    import Vue from 'vue'
    import child from "./child"
    export default {
        name: 'parent',
        components: {
            child
        },
        data() {
            return {
                pData: '我是父组件数据'
            }
        },
        //初始用法        
        // provide() {
        //     return {
        //         parentData: this.pData,//该方式绑定的数据不是响应式的,即祖先组件中parentData变化,后代组件中不会跟着变
        //     }
        // },
        // methods: {
        //     changeData() {
        //         this.pData = "我是改变以后的父组件数据";
        //     }
        // }

        //方法一        
        // provide() {
        //     return {
        //         parentData: this,//provide祖先组件的实例
        //     }
        // },
        // methods: {
        //     changeData() {
        //         this.pData = "我是改变以后的父组件数据1";
        //     }
        // }

        //方法二       
        provide() {
            this.parentData = Vue.observable({
                pData: this.pData
            });
            return {
                parentData: this.parentData
            }
        },
        methods: {
            changeData() {
                this.parentData.pData = "我是改变以后的父组件数据2";
            }
        }
    }
</script>
<!--child.vue-->
<template>
    <div>{{parentData.pData}}</div>
</template>

<script>
    export default {
        name: 'child',
        inject: {
            parentData: {
                default: () => { }
            }
        }
    }
</script>

另外, provideinject 主要为高阶插件/组件库提供用例,不推荐直接用于应用程序代码,可视情况采用。

5. ref

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

<!--parent.vue-->
<template>
    <div>
        <p>我是父组件获取的子组件的数据:{{pData}}</p>
        <child ref='compChild'></child>
    </div>
</template>
<script>
    import child from './child'
    export default {
        name: 'parent',
        components: {
            child
        },
        data() {
            return {
                pData: ''
            }
        },
        methods: {
            parentEvent() {
                let compChild = this.$refs.compChild;//通过this.$refs获取子组件实例
                this.pData = compChild.childData;//获取子组件数据
                compChild.childEvent();//调用子组件方法
            }
        },
        mounted() {
            this.parentEvent();
        },
    }
</script>
<!--child.vue-->
<template>
    <div></div>
</template>
<script>
    export default {
        name: 'child',
        data() {
            return {
                childData: '我是child的数据'
            }
        },
        methods: {
            childEvent() {
                console.log('我是child的方法');
            }
        }
    }
</script>

6. EventBus

中央事件总线(EventBus)可以巧妙而轻量地实现任何组件间的通信,包括父子、兄弟、跨级。如深入使用,可以扩展 bus 实例,给它添加 data、methods、computed 等选项,进行公用,业务中,一些需要共享的通用信息如用户登录信息,授权token等,只需在初始化时让 bus 获取一次,任何时间、组件就可以直接使用,在协同开发及单页应用(SPA)中特别实用。但是,当项目较大时,这种方式不太容易维护,可以选择后面要说的状态管理解决方案 Vuex。

//eventBus.js
import Vue from 'vue'
export const bus = new Vue();
<!--compA.vue-->
<template>
    <div>
        <comp-b></comp-b>
        <comp-c></comp-c>
    </div>
</template>
<script>
    import compB from './compB'
    import compC from './compC'
    export default {
        name: 'compA',
        components: {
            compB, compC
        }
    }
</script>
<!--compB.vue-->
<template>
    <div>
        <p>compB:{{dataB}}</p>
        <button @click='handleEventB'>点击emit组件compB的数据</button>
    </div>
</template>
<script>
    import { bus } from './eventBus'
    export default {
        name: 'compB',
        data() {
            return {
                dataB: '我是组件compB中的数据'
            }
        },
        methods: {
            handleEventB() {
                bus.$emit('on-msg', this.dataB);//发送事件
            }
        }
    }
</script>
<!--compC.vue-->
<template>
    <div>
        <p>{{dataC}}</p>
    </div>
</template>
<script>
    import { bus } from './eventBus'
    export default {
        name: 'compC',
        data() {
            return {
                dataC: '我是组件compC中的数据'
            }
        },
        methods: {
            handleEventC() {
                //接收事件
                bus.$on('on-msg', val => {
                    this.dataC = val;
                })
            }
        },
        mounted() {
            this.handleEventC();
        },
        beforeDestroy() {
            bus.$off('on-msg', {})//移除事件监听
        },
    }
</script>

7. Vuex

Vuex 是一个专为 Vue 服务,用于管理页面数据状态、提供统一数据操作的生态系统。它集中于 MVC 模式中的 Model 层,规定所有的数据操作必须通过 action - mutation - state change 的流程来进行,再结合 Vue 的数据视图双向绑定特性来实现页面的展示更新。
在这里插入图片描述
Vuex 各模块的主要功能:

  • Vue Components: Vue组件。HTML页面上,负责接收用户操作等交互行为,执行 dispatch 方法触发对应 action 进行回应。
  • dispatch: 操作行为触发方法,是唯一能执行 action 的方法。
  • actions: 操作行为处理模块。负责处理 Vue Components 接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他 action 以及提交 mutation 的操作。该模块提供了 Promise 的封装,以支持 action 的链式触发。
  • commit: 状态改变提交操作方法。对 mutation 进行提交,是唯一能执行mutation 的方法。
  • mutations: 状态改变操作方法。是 Vuex 修改 state 的唯一推荐方法,其他修改方式在严格模式下将会报错。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些 hook 暴露出来,以进行 state 的监控等。
  • state: 页面状态管理容器对象。集中存储 Vue components 中 data 对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用 Vue 的细粒度数据响应机制来进行高效的状态更新。
  • getters: state 对象读取方法。图中没有单独列出该模块,应该被包含在了render 中,Vue Components 通过该方法读取全局state对象。

下面看个实例:

//main.js 入口文件
import Vue from 'vue'
import compA from './compA'
import store from './store';  //使用store

Vue.config.productionTip = false;
new Vue({
    store,  //关联store
    render: h => h(compA)
}).$mount("#app")
//store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
    dataB: '',
    dataC: ''
}

const mutations = {
    setDataB(state, data) {
        // 将compA组件的数据存放于state
        state.dataB = data
    },
    setDataC(state, data) {
        // 将compB组件的数据存放于state
        state.dataC = data
    }
}

export default new Vuex.Store({
    state,
    mutations
})
<!--compA.vue-->
<template>
    <div>
        <comp-b></comp-b>
        <comp-C></comp-c>
    </div>
</template>
<script>
    import compB from './compB'
    import compC from './compC'
    export default {
        name: 'compA',
        components: {
            compB, compC
        }
    }
</script>
<!--compB.vue-->
<template>
    <div>
        <h2>我是compB组件</h2>
        <p>compB组件获取到的数据:{{showDataB}}</p>
        <button @click="handleEventB">点击将compB的数据传给compC</button>
    </div>
</template>
<script>
    export default {
        name: 'compB',
        data() {
            return {
                dataB: '我是compB的数据'
            }
        },
        computed: {
            showDataB() {
                return this.$store.state.dataB//获取数据dataB
            }
        },
        methods: {
            handleEventB() {
                this.$store.commit('setDataC', this.dataB);//修改数据dataC
            }
        }
    }
</script>
<!--compC.vue-->
<template>
    <div>
        <h2>我是compC组件</h2>
        <p>compB组件获取到的数据:{{showDataC}}</p>
        <button @click="handleEventC">点击将compC的数据传给compB</button>
    </div>
</template>
<script>
    export default {
        name: 'compC',
        data() {
            return {
                dataC: '我是compC的数据'
            }
        },
        computed: {
            showDataC() {
                return this.$store.state.dataC//获取数据dataC
            }
        },
        methods: {
            handleEventC() {
                this.$store.commit('setDataB', this.dataC);//修改数据dataB
            }
        }
    }
</script>

效果如下:
在这里插入图片描述
Vuex 存储的数据是响应式的,但并不会保存,刷新之后会回到初始状态,要解决这个问题,可以结合下面要说的 localStorage 来实现,当 Vuex 中数据变化时,将数据存储到 localStorage 中,刷新之后,如果 localStorage 中有数据,取出来替换 store 中的 state

8. localStorage / sessionStorage

HTML5 的本地存储 API 中的 localStoragesessionStorage 在使用方法上是相同的,区别在于 sessionStorage 在关闭页面后即被清空,而 localStorage 则会一直保存。存储的内容是以 Json 的形式存储的,JSON.parse() 用于将一个 JSON 字符串转换为对象,JSON.stringify() 可以将对象转换为字符串。

  • sessionSorage: 用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些数据。
  • localSorage: 用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去除。

保存数据到本地:

sessionStorage.setItem('key', JSON.stringify(value));
localStorage.setItem('key', JSON.stringify(value));

取得本地的数据:

let data1 = JSON.parse(sessionStorage.getItem('key'));
let data2 = JSON.parse(localStorage.getItem('key'));

清空全部数据:

sessionStorage.clear()
localStorage.clear()

删除单个数据:

localStorage.removeItem(key);
sessionStorage.removeItem(key);

得到某个索引的key:

localStorage.key(index);
sessionStorage.key(index);

三、总结

综上所述,Vue 组件通信的方式大概有八大类:

  1. props / $emit
  2. $children / $parent
  3. $attrs / $listeners
  4. provide / inject
  5. ref
  6. EventBus
  7. Vuex
  8. localStorage / sessionStorage

按组件间的关系对应合适的使用场景可大致归纳如下:

  • 父子组件通信:
    props / $emit , $parent / $children , $attrs / $listeners , provide / inject , ref , EventBus , Vuex , localStorage/sessionStorage
  • 兄弟组件通信:
    EventBus , Vuex , localStorage/sessionStorage
  • 跨级组件通信:
    $attrs / $listeners , provide / inject , EventBus , Vuex , localStorage/sessionStorage

四、其他

本文所示的例子都已上传至 github,都采用快速原型开发,如果需要可参考以下步骤:

  1. 使用如下命令安装 vue-cli3
npm install @vue/cli -g

或者

yarn global add @vue/cli
  1. 使用如下命令安装一个额外的全局插件,这样就可以使用 vue serve 和 vue build 命令独立运行单个 * .vue 文件:
npm install -g @vue/cli-service-global

或者

yarn global add @vue/cli-service-global 
  1. 新建 *.vue 文件
  2. 在 *.vue 文件所在目录下运行如下命令:
# App.vue
vue serve
# 指定入口文件
vue serve component.vue

参考文章

  1. 珠峰架构课(强烈推荐)
  2. Vue.js 官方文档
  3. Vue.js实战 梁灏编著
  4. Vue组件间通信6种方式
  5. Vuex框架原理与源码分析
  6. localStorage 与 sessionStorage 使用方式
  7. Vue $dispatch 和 $broadcast
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值