32

Vue组件通讯

 5 years ago
source link: https://www.w3cplus.com/vue/component-communication.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

使用Vue构建组件容易,但对于初学者要掌握Vue组件中的通讯还是有一定的难度。比如说,父组件如何向子组件通讯?子组件又是如何向父组件通讯?兄弟组件又是怎么通讯?这些方面都是有关于组件通讯相关的知识。而且掌握Vue组件之间的通讯方式还是掌握Vue组件的另一种能力。

在Vue中,Vue的组件自身就很棒,因为它可以帮助我们使用重用的代码片段,我们也可以使用Vue提供的所有功能。现在我们要掌握怎么建立组件之间的连接,也就是组件的通讯,以便一个组件中的操作可以更新应用程序中的其他组件。在接下来的内容中,咱们会涉及两个部分,第一个部分是父子组件怎么通讯;第二部分兄弟组件怎么通讯?

父子组件通讯

先来看看父子组件之间如何建立通讯: 将看到父组件如何向子组件通讯和子组件如何向父组件通讯

父组件向子组件通讯

要在Vue中将数据从父组件传到子组件,我们可以通过 props 来实现。在React中也是使用类似的约定来实现组件之间的数据共享。 props 指的是从外部设置的属性,例如来自父组件。为了告诉Vue子组件从自已实例的外部接收数据,需要在子组件的 Vue 对象中设置 props 属性。这个属性包含一个 String 数组,每个字符串表示一个可以从父组件设置的属性。请注意, props 严格用于父组件与子组件之间的单向通讯,并且你不希望尝试直接在子组件中更改 props 的值。否则,你将收到类似这样的错误信息“避免直接修改某个 prop ,因为当父组件重新渲染时,该值将被覆盖” 这样的错误。相反,根据 prop 的值使用 datacomputed

父组件使用属性绑定

为了将数据从父组件传到子组件,在父组件中设置一个属性,该属性绑定和子组件的 prop 相同的名称一个属性值。请注意,我们现在位于父组件内部,但是我们使用了自定义标签 <child-card> 来渲染子组件。在这个标记上,我们设置了绑定属性。在下面的示例中,使用 parentMessage 作为属性名,所以在子组件中需要一个 props: ['parentMessage'] 作为一个 prop 。然后在父组件中,使用 <child-card :parentMessage = "parentMessage"></child-card> 来传递数据。

<!-- ParentCard.vue -->
<template>
    <div class="card">
        <div class="card-header">
            <h5 v-text="theCardTitle"></h5>
            <button @click="sendMessage" class="btn">给子组件发送一个消息</button>
        </div>
        <div class="card-body">
            <child-card :parentMessage="parentMessage"></child-card>
        </div>
    </div>
</template>

<script>
    import ChildCard from './ChildCard';

    export default {
        name: 'ParentCard',
        data: () => ({
            theCardTitle: '父组件',
            parentMessage: ''
        }),
        components: {
            ChildCard
        },
        methods: {
            sendMessage() {
                this.parentMessage = `<b>消息来自父组件:</b> (^_^)!!!`
            }
        }
    }
</script>

子组件使用props对象

接下来的代码中,创建一个 ChildCard 子组件,它的 data 中有一个 props 数组,并且设置了一个 parentMessage 字符串。这表明 parentMessage 可以从外部设置,也可以从父组件设置。这正是我们在上一节中所做的。为 prop 提供的字符串名称,在我们的示例中, parentMessage 必须与此组件中模板部分中使用的属性名相匹配。

<!-- ChildCard.vue -->
<template>
    <div class="message">
        <div class="message-header">
            <h5 v-text="theCardTitle"></h5>
        </div>
        <div class="message-body">
            <p class="message-text" v-html="theCardBody"></p>
            <div v-if="parentMessage" class="alert" v-html="parentMessage"></div>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'ChildCard',
        props: ['parentMessage'],
        data: () => ({
            theCardTitle: '子组件',
            theCardBody: '我是一个子组件!(^_^) !!!'
        })
    }
</script>

上面的两小节中,分别创建了 ParentCardChildCard 两个组件,而且子组件 ChildCard 嵌套在父组件 ParentCard 中。在子组件中,使用了 v-if 指令有条件地显示来自父组件 ParentCard 的消息,并且显示在 div.alert 中。如果没有消息,则不会显示 .alert 。因此,当页面首次渲染时, parentMessage 的初始值是一个空字符串(在 ParentCard 组件的 data 中设置了 parentMessage 为空字符串)。所以我们一开始渲染页面的时候,并看不到( ChildCard 子组件不显示 .alert )。当用户点击了 ParentCard 组件中的“发送消息”的按钮时,则会触发 ParentCard 组件中定义的 sendMessage() 方法。这个时候, parentMessage 的值就变成了 <b>消息来自父组件:</b> (^_^)!!! 。由于此变量使用 :parentMessage="parentMessage" 绑定到 <child-card> 标签上,并且子组件 ChildCard 通过 props:['parentMessage'] 接受该值,如此一来,子组件将使用来自父组件的 parentMessage 的值。

最终效果如下:

当你点击示例中右上角的按钮,就可以看到父组件向子组件发送的消息,这样就完成了父组件向子组件的数据通讯:

Fbuyqe6.gif

简单的总结一下:

在Vue中,父组件向子组件传递数据(通讯),可以借助 props 属性完成。

可以用一张类似下面这样的图来描述父组件向子组件通讯的关系:

EBJZZrz.png!web

子组件向父组件通讯

事实除了从父组件向子组件传数据之外,有时候也要能从子组件向父组件传数据。那么问题来了,在Vue中如何从子组件向父组件传数据(通讯)?在Vue中要实现这个,我们可以在子组件中发出自定义事件,并在父组件中侦听发出的事件(子组件中自定义的事件)。我们在上面的示例上来做一些更改,完成一个子组件向父组件通讯的示例。

子组件发出自定义事件

首先在子组件 ChildCard<template> 中添加一个新的标签 button 。在这个 button 添加一个 click 事件:

<!-- ChildCard.vue -->
<template>
    <div class="message">
        <div class="message-header">
            <h5 v-text="theCardTitle"></h5>
        </div>
        <div class="message-body">
            <p class="message-text" v-html="theCardBody"></p>
            <div v-if="parentMessage" class="alert" v-html="parentMessage"></div>
            <button v-if="parentMessage" @click="ok" class="btn">OK</button>
        </div>
    </div>
</template>

当我们点击“OK”按钮时,想运行名为 ok() 的方法。让我们设置该方法,以便在触发事件时发出( $emit )自定义的事件。我们将一个 finished 字符串传递给 $emit 函数。当然我们可以选择自己喜欢的名字,但在这个案例中我们选择了 finished 这个名,这样更具语义化。我们希望子组件的信息发送到父组件。

<!-- ChildCard.vue -->
<script>
    export default {
        name: 'ChildCard',
        props: ['parentMessage'],
        data: () => ({
            theCardTitle: '子组件',
            theCardBody: '我是一个子组件!(^_^) !!!'
        }),
        methods: {
            ok() {
                this.$emit('finished')
            }
        }
    }
</script>

父组件侦听自定义事件

现在我们可以回到父组件 ParentCard中,在父组件中使用自定义标签 调用子组件 ChildCard,在这个标签中我们可以使用 @finished="finished" 侦听子组件中自定义的事件。这意味着我们需要在父组件中定义一个 finished() 方法。父组件中定制了自定义属性的侦听器和触发它的方法。

<!-- ParentCard.vue -->
<template>
    <div class="card">
        <div class="card-header">
            <h5 v-text="theCardTitle"></h5>
            <button @click="sendMessage" class="btn">给子组件发送一个消息</button>
        </div>
        <div class="card-body">
            <child-card :parentMessage="parentMessage" @finished="finished"></child-card>
        </div>
    </div>
</template>

<script>
    import ChildCard from "./ChildCard";

    export default {
        name: "ParentCard",
        data: () => ({
            theCardTitle: "父组件",
            parentMessage: ""
        }),
        components: {
            ChildCard
        },
        methods: {
            sendMessage() {
                this.parentMessage = `<b>消息来自父组件:</b> (^_^)!!!`;
            },
            finished() {
                this.parentMessage = ''
            }
        }
    };
</script>

在这个示例中,用户首先点击“发送消息”按钮,它将消息向下发送到子组件,这个时候消息和一个新按钮会一起在子组件中渲染。

3Yj6Zb2.png!web

现在我们可点击“OK”按钮,它会向父组件发出自定义的 finished 事件。在父组件中,我们正在侦听该自定义事件,当侦听到 finished 自定义事件时,就会触发 finished() 方法,将 parentMessage 重置为空字符串。现在这个示例,我们实现了父组件向子组件和子组件向父组件传递数据(数据通讯)。

同样的,我们可以用张图来描述:

aaIjQr7.png!web

通过回调函数实现子组件向父组件通讯

上面的示例是通过自定义事件完成子组件向父组件进行数据通讯。如果你不想发出自定义事件,还可以通过另一种方式将消息从子组件发送到父组件。和上一个示例不同之处是,我们不需要在子组件中定义 ok() 方法,而是在父组件中定义该方法。一旦我们在父组件上定义了该方法,就可以通过 props 把信息从父组件传递给子组件。所以在 ParentCard 组件中定义 ok() 方法,并且在 <child-card> 上绑定已定义该方法和 props

<!-- ParentCard.vue -->
<template>
    <div class="card">
        <div class="card-header">
            <h5 v-text="theCardTitle"></h5>
            <button @click="sendMessage" class="btn">给子组件发送一个消息</button>
        </div>
        <div class="card-body">
            <child-card :parentMessage="parentMessage" @finished="finished" :ok="ok"></child-card>
        </div>
    </div>
</template>

<script>
    import ChildCard from "./ChildCard";

    export default {
        name: "ParentCard",
        data: () => ({
            theCardTitle: "父组件",
            parentMessage: ""
        }),
        components: {
            ChildCard
        },
        methods: {
            sendMessage() {
                this.parentMessage = `<b>消息来自父组件:</b> (^_^)!!!`;
            },
            finished() {
                this.parentMessage = ''
            },
            ok() {
                this.finished()
            }
        }
    };
</script>

现在我们要做的是更新子组件上的 props 。这样做的目的是通过 props 将回调函数从父组件中传递到子组件。我们可以像下面这样做:

<!-- ChildCard.vue -->
<template>
    <div class="message">
        <div class="message-header">
            <h5 v-text="theCardTitle"></h5>
        </div>
        <div class="message-body">
            <p class="message-text" v-html="theCardBody"></p>
            <div v-if="parentMessage" class="alert" v-html="parentMessage"></div>
            <button v-if="parentMessage" @click="ok" class="btn">OK</button>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'ChildCard',
        props: ['parentMessage', 'ok'],
        data: () => ({
            theCardTitle: '子组件',
            theCardBody: '我是一个子组件!(^_^) !!!'
        })
    }
</script>

父组件和子组件之间的交互与上面的示例是相同的。

你可以使用任何对你来说比较容易的方案,但是在从子组件到父组件的通讯中,发出自定义事件似乎是较为更流行的一种。

现在我们知道如何通过 props 实现父组件向子组件之间的通讯以及如何通过自定义事件完成子组件向父组件之间的通讯。除了这两种之外,还有另外一种情形,那就是兄弟之间的组件如何进行数据通讯。那么接下来,咱们就来学习这方面的知识。

兄弟组件通讯

在Vue中实现兄弟组件的通讯也有几种方法,其中一种方法是让父组件允当两个子组件之间的中间件(中继);另一种就是使用 EventBus (事件总线),它允许两个子组件之间直接通讯,而不需要涉及父组件。

通过父组件进行兄弟组件之间通讯

先来看第一个方法,就是 让兄弟组件通过一个共同的父组件彼此通讯

我们还是通过示例来学习。接下来的这个示例包含父组件和两个子组件,这两个子组件是兄弟组件。单击兄弟组件上的按钮,可以看到他们之间可以相互通讯。

M77BBnI.gif

首先创建 ParentCard 组件:

<!-- ParentCard.vue -->
<template>
    <div class="card">
        <div class="card-header">
            <h5 v-text="theCardTitle"></h5>
            <button @click="momSaidChill" v-if="stopFighting()" class="btn">停止通讯</button>
        </div>
        <div class="card-body">
            <brother-card :messageSon="messageson" @brotherSaid="messageDaughter($event)"></brother-card>
            <sister-card :messageDaughter="messagedaughter" @sisterSaid="messageSon($event)"></sister-card>
        </div>
    </div>
</template>

<script>
    import BrotherCard from './BrotherCard';
    import SisterCard from './SisterCard'

    export default {
        name: 'ParentCard',
        data: () => ({
            theCardTitle: '父组件',
            messagedaughter:'', 
            messageson:''
        }),
        components: {
            BrotherCard,
            SisterCard
        },
        methods: {
            messageDaughter(message) {
                this.messagedaughter = message;
            },
            messageSon(message) {
                this.messageson = message;
            },
            stopFighting() {
                if (this.messagedaughter && this.messageson) {
                    return true
                }
                return false
            },
            momSaidChill() {
                this.messagedaughter = '',
                this.messageson = ''
            }
        }
    };
</script>

创建 SisterCard 组件:

<!-- SisterCard.vue -->
<template>
    <div class="message">
        <div class="message-header">
            <h5 v-text="theCardTitle"></h5>
        </div>
        <div class="message-body">
            <p class="message-text">我是Sister组件</p>
            <button @click="messageBrother" class="btn">给哥哥发消息</button>
            <div v-if="messageDaughter" class="alert" v-html="messageDaughter"></div>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'SisterCard',
        props: ['messageDaughter'],
        data: () => ({
            theCardTitle: '子组件2'
        }),
        methods: {
            messageBrother() {
                this.$emit('sisterSaid', '妈妈说,该做作业了!(^_^)!!!')
            }
        }
    }
</script>

接着创建 BrotherCard 组件:

<!-- BrotherCard.vue -->
<template>
    <div class="message">
        <div class="message-header">
            <h5 v-text="theCardTitle"></h5>
        </div>
        <div class="message-body">
            <p class="message-text">我是Brother组件</p>
            <button @click="messageSister" class="btn">给妹妹发消息</button>

            <div v-if="messageSon" class="alert" v-html="messageSon"></div>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'BrotherCard',
        props: ['messageSon'],
        data: () => ({
            theCardTitle: '子组件1'
        }),
        methods: {
            messageSister() {
                this.$emit('brotherSaid', '妈妈说,该做作业了!(^_^)!!!')
            }
        }
    }
</script>

最终效果如下:

接下来简单看看这个实现过程。

SisterCard 通过 ParentCardBrotherCard 通讯

首先来看 SisterCard 是如何与 BrotherCard 通讯的。从示例中可以看出,他们两之间的通讯是通过其父组件 ParentCard 作为中间媒介来进行通讯的。

我们在 SisterCard 组件的 <template> 中为 messageBrother() 方法设置了一个 @click 事件来监听该事件。

<button @click="messageBrother" class="btn">给哥哥发消息</button>

当用户点击 SisterCard 中的“给哥哥发消息”将会触发 messageBrother() 方法。在这个方法中,将发出一个 sisterSaid 事件,并且把 妈妈说,该做作业了!(^_^)!!! 信息发送出去。

methods: {
    messageBrother() {
        this.$emit("sisterSaid", "妈妈说,该做作业了!(^_^)!!!");
    }
}

ParentCard<template> 中定制了一个 @sisterSaid 事件侦听器,它触发了 messageSon() 方法。所以父组件在这两个兄弟组件之间起到了传递的作用。

<sister-card :messageDaughter="messagedaughter" @sisterSaid="messageSon($event)"></sister-card>

另外在 ParentCard 组件中声明了 messageSon() 方法,该方法接受上面发送的自定义事件,并将其设置为 messageson 属性。

messageSon(message) {
    this.messageson = message;
},

这样一来, ParentCard 组件中 messageson 就由空字符串变成了 妈妈说,该做作业了!(^_^)!!!

接着在 ParentCard 组件自定义标签 <brother-card> 通过 :messageSon="messageson" 的方式将 messageson 属性绑定到 <brother-card>

<brother-card :messageSon="messageson" @brotherSaid="messageDaughter($event)"></brother-card>

这个时候在 BrotherCard 组件中设置 props 的属性值为 messageSon 。这样就可以访问源自于 SisterCard 组件的数据,并且该数据在 BrotherCard 中显示。

props: ["messageSon"],

最后在 BrotherCard 组件就可以使用该数据。我们可以通过 v-if 指令来查看 messageSon 是否有任何有用的数据,如果有,那么就在 div.alert 中显示该消息:

<div v-if="messageSon" class="alert" v-html="messageSon"></div>

上面的描述过程也适用于 BrotherCard 通过 ParentCardSisterCard 进行数据通讯。

通过EventBus进行兄弟间组件通讯

随着应用程序越来越庞大,通过父组件来传递所有内容会把事情变得越来越棘手。不过我们还有另一种选择,那就是使用 EventBus 架起兄弟之间通讯的桥梁。接下来看看我们是如何利用这一点一完成兄弟组件之间的数据通讯。

我们同样基于上面的示例来做修改。接下来的示例中, ParentCard 组件包含了 SisterCardBrotherCard 两个子组件,而且这两个子组件是兄弟组件。

首先在 main.js 文件中定义一个新的 eventBus 对象,其实他是一个全新的Vue实例:

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

export const eventBus = new Vue()

new Vue({
    el: '#app',
    render: h => h(App)
})

接着在新创建的 BrotherCard 组件导入 main.js

<!-- BrotherCard.vue -->

<script>
    import { eventBus } from '../main'
</script>

eventBus 实例现在将成为 BrotherCard 组件中发出事件的实例。现在我们可以使用 eventBus.$emit 来替代上例中的 this.$emiteventBus 是一个Vue实例,而且 eventBus 有这个 $emit 方法,这就是我们能够这么用的原因。这样做同样会触发相同的自定义事件名称和消息。

methods: {
    messageSister() {
        eventBus.$emit('brotherSaid', '妈妈说,该做作业了!(^_^)!!!')
    }
}

同样可以在 SisterCard 组件中引入 eventBus

<script>
    import { eventBus } from '../main'
</script>

created() 生命周期钩子添加到 SisterCard 组件。在 created() 钩子中添加 eventBus 启动自定义事件的侦听器。当使用 SisterCard 组件时,该侦听器将开始运行并且会保持运行。下面的代码只是侦听 brotherSaid 自定义事件,然后触发回调,将作为自定义事件有效负载传递的消息分配给 fromBrother

created() {
    eventBus.$on('brotherSaid', (message) => {
        this.fromBrother = message
    })
}

这样就可以有条件地显示来自 BrotherCard 的信息:

<div v-if="fromBrother" class="alert" v-html="fromBrother"></div>

上面看到的是如何通过 eventBus 实现 SisterCardBrotherCard 传递数据的方式,反之, BrotherCard 向SisterCard`传递数据也可以使用类似的方式。

最终代码如下:

<!-- SisterCard.vue -->
<template>
    <div class="message">
        <div class="message-header">
            <h5 v-text="theCardTitle"></h5>
        </div>
        <div class="message-body">
            <p class="message-text">我是Sister组件</p>
            <button @click="messageBrother" class="btn">给哥哥发消息</button>

            <div v-if="fromBrother" class="alert" v-html="fromBrother"></div>
        </div>
    </div>
</template>

<script>
    import { eventBus } from "../main";

    export default {
        name: "SisterCard",
        data: () => ({
            theCardTitle: "Sister Card",
            fromBrother: ""
        }),
        methods: {
            messageBrother() {
                eventBus.$emit("sisterSaid", "妈妈说,该做作业了!(^_^)!!!");
            }
        },
        created() {
            eventBus.$on("brotherSaid", message => {
                this.fromBrother = message;
            });
        }
    };
</script>

<!-- BrotherCard.vue -->
<template>
    <div class="message">
        <div class="message-header">
            <h5 v-text="theCardTitle"></h5>
        </div>
        <div class="message-body">
            <p class="message-text">我是Brother组件</p>
            <button @click="messageSister" class="btn">给妹妹发消息</button>

            <div v-if="fromSister" class="alert" v-html="fromSister"></div>
        </div>
    </div>
</template>

<script>
    import { eventBus } from "../main.js";

    export default {
        name: "BrotherCard",
        data: () => ({
            theCardTitle: "Brother Card",
            fromSister: ""
        }),
        methods: {
            messageSister() {
                eventBus.$emit("brotherSaid", "妈妈说,该做作业了!(^_^)!!!");
            }
        },
        created() {
            eventBus.$on("sisterSaid", message => {
                this.fromSister = message;
            });
        }
    };
</script>

最后创建的 ParentCard 组件,我们可以像下面这样编码:

<!-- ParentCard -->
<template>
    <div class="card">
        <div class="card-header">
            <h5 v-text="theCardTitle"></h5>
        </div>
        <div class="card-body">
            <brother-card></brother-card>
            <sister-card></sister-card>
        </div>
    </div>
</template>

<script>
    import BrotherCard from "./BrotherCard";
    import SisterCard from "./SisterCard";

    export default {
        name: "ParentCard",
        data: () => ({
            theCardTitle: "Parent Card"
        }),
        components: {
            BrotherCard,
            SisterCard
        }
    };
</script>

最终看到的效果如下:

总结

在本教程中,我们学习了在Vue中如何实现组件之间的通讯。通过实例看到了如何实现父组件向子组件,子组件向父组件以及兄弟组件间的数据通讯。简单的根据为:

props
eventBus

最后用一张图来简单的描述一下:

26v6jei.jpg!web

参考阅读


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK