1. 程式人生 > >Vue 兄弟元件之間的通訊

Vue 兄弟元件之間的通訊

使用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的值。

最終效果如下:

當你點選示例中右上角的按鈕,就可以看到父元件向子元件傳送的訊息,這樣就完成了父元件向子元件的資料通訊:

簡單的總結一下:

在Vue中,父元件向子元件傳遞資料(通訊),可以藉助props屬性完成。

可以用一張類似下面這樣的圖來描述父元件向子元件通訊的關係:

子元件向父元件通訊

事實除了從父元件向子元件傳資料之外,有時候也要能從子元件向父元件傳資料。那麼問題來了,在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>

在這個示例中,使用者首先點選“傳送訊息”按鈕,它將訊息向下傳送到子元件,這個時候訊息和一個新按鈕會一起在子元件中渲染。

現在我們可點選“OK”按鈕,它會向父元件發出自定義的finished事件。在父元件中,我們正在偵聽該自定義事件,當偵聽到finished自定義事件時,就會觸發finished()方法,將parentMessage重置為空字串。現在這個示例,我們實現了父元件向子元件和子元件向父元件傳遞資料(資料通訊)。

同樣的,我們可以用張圖來描述:

通過回撥函式實現子元件向父元件通訊

上面的示例是通過自定義事件完成子元件向父元件進行資料通訊。如果你不想發出自定義事件,還可以通過另一種方式將訊息從子元件傳送到父元件。和上一個示例不同之處是,我們不需要在子元件中定義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(事件匯流排),它允許兩個子元件之間直接通訊,而不需要涉及父元件。

通過父元件進行兄弟元件之間通訊

先來看第一個方法,就是讓兄弟元件通過一個共同的父元件彼此通訊

我們還是通過示例來學習。接下來的這個示例包含父元件和兩個子元件,這兩個子元件是兄弟元件。單擊兄弟元件上的按鈕,可以看到他們之間可以相互通訊。

首先建立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來讓兄弟之間元件進行資料通訊

最後用一張圖來簡單的描述一下: