Vue非父子组件之间的传值

 

我们可以把一个网页拆分成很多部分,每个部分就是我们代码中的一个组件,左侧整个区域代表方块1,拆分成3个灰色区域代表方块2,左下区域又分成更小的2个深色区域,分别用2个方块3表示,右侧拆分成更小的3个深色区域用3个方块3表示

所以,左侧的网页就可以用右侧的图来表示,一个复杂的网页,最终都可以拆分成小的组件。

右边的图,左上角的红线是表示父子组件传值,父组件通过props向子组件传值,子组件通过$emit触发向父组件传值。

中间的红线表示非父子传值(爷孙也是非父子),当然可以组件1通过props向子组件2传值,组件2通过props向子组件3传值。子组件3通过$emit触发向父组件2传值,子组件2通过$emit触发向父组件1传值。但是这种传值也很麻烦。

最下面这根红线表示非父子传值,当然你也可以通过和上面一样的方法一层一层的传值,但是代码将会变得无比复杂!

而官方对vue定义是轻量级的视图层框架,当出现了非常复杂的数据传递的时候,光靠着vue是解决不了的!

非父子组件传值一般2种方式:

官方提供的数据层框架vuex

利用发布订阅模式来解决(在vue中称为总线机制)

我们这里讲解第二种

直接来看代码例子

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>非父子组件间的传值(Bus/总线/发布订阅模式/观察者模式)
        </title>
        <script src="https://unpkg.com/vue"></script>
    </head>
    <body>
        <div id="app">
            <child content="lcy"></child><!-- 两个child是兄弟组件,不是父子关系 -->
            <child content="真的帅"></child>
        </div>
        <script> 
            Vue.prototype.bus = new Vue();
            Vue.component('child', {
                props: {
                    content: String
                },
                template: '<div @click="handleClick">{{content}}</div>',
                methods: {
                    handleClick() {
                        this.bus.$emit('change', this.content);
                    }
                },
                mounted() {
                    this.bus.$on('change', function(msg){
                        alert(msg);
                    })
                }
            })
            var vm = new Vue({
                el: "#app",
                data: {

                },
            })
        </script>
    </body>
</html>

运行结果

点击lcy之后弹出两次alert对话框"lcy"

点击真的帅之后弹出两次alert对话框"真的帅"

为什么是两次呢?

每个组件都是vue实例,我们在Vue原型中定义bus属性,这是一个vue实例,相当于全局总线,等同在ES6的class Vue中定义,只要以后new Vue实例或者创建组件的时候,每个组件上都会有bus这个属性,指向同一个Vue实例。

因为每个组件都会去挂载,挂载完之后会执行生命周期方法mounted方法,而在mounted方法里,我们的全局总线bus实例注册了对change事件的监听,所以每个组件都有对change事件的监听,$on监听当前实例bus上的自定义事件change。事件可以由vm.$emit触发。触发后执行这里的回调函数,回调函数会接收所有传入事件触发函数的额外参数。

子组件child绑定了点击事件,点击后执行handleClick方法,方法this.bus.$emit('change', this,content)的执行会触发当前实例bus上监听的事件change后面的附加参数this.content会传给监听器回调函数而总线bus是每个组件都有的,所以触发了所有组件上监听的change事件,change事件的回调函数获取参数content,弹出alert框。这里如果点的"lcy",$emit附加的content就是"lcy",所以回调接收到的是"lcy",alert弹出2次"lcy"

 

 

现在的目标是希望点击其中一个组件的时候,另一个组件跟着改变自己的内容

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>非父子组件间的传值(Bus/总线/发布订阅模式/观察者模式)
        </title>
        <script src="https://unpkg.com/vue"></script>
    </head>
    <body>
        <div id="app">
            <child content="lcy"></child>
            <child content="真的帅"></child>
        </div>
        <script> 
            Vue.prototype.bus = new Vue();
            Vue.component('child', {
                props: {
                    content: String
                },
                data() {
                    return {
                        myContent: this.content
                    }
                },
                template: '<div @click="handleClick">{{myContent}}</div>',
                methods: {
                    handleClick() {
                        this.bus.$emit('change', this.myContent);
                    }
                },
                mounted() {
                    this.bus.$on('change', (msg) => {
                       this.myContent = msg;
                    })
                }
            })
            var vm = new Vue({
                el: "#app",
                data: {

                },
            })
        </script>
    </body>
</html>

运行结果

这里为什么要在data里面加myContent : content呢?我直接改props里面的content不就可以实现效果了吗?

效果是可以实现,但是会报错,如下

每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。所以才会建立一个副本(不是引用相同地址)myContent去解决这个警告。

尽管运行正常,为什么要报这个警告呢?

试想,父组件content传的不是字符串,传的是自定义对象{name : "xxx"},现在在子组件直接修改这个对象this.content.name="aaa",结果就影响了父组件,如果父组件其他地方还引用这个对象就出现了意料之外的结果。

所以需要一个副本(不是指向同一个引用)myContent : content

注意:data{}中定义的对象不会相等!就是上面这个例子。定义数字和字符串因为复用常量池数据,会相等。

官方文档参考见这里单向数据流

 

 

关注、留言,我们一起学习。

 

===============Talk is cheap, show me the code================

 

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页