Vue - $attrs及$listeners屬性實現(xiàn)多級嵌套組件通信

前言

組件是 vue.js 最強大的功能之一,而組件實例的作用域是相互獨立的,這就意味著不同組件之間的數(shù)據(jù)無法相互引用。

如上圖所示,A 和 B、B 和 C都是父子關(guān)系,A 和 C 是隔代關(guān)系(可能隔多代)。針對不同的使用場景,如何選擇行之有效的通信方式?

A 組件與 B 組件之間的通信: (父子組件)

如上圖所示,A、B、C三個組件依次嵌套,按照 Vue 的開發(fā)習(xí)慣,父子組件通信可以通過以下三種方式實現(xiàn):

1、A To B 通過props的方式向子組件傳遞數(shù)據(jù),B To A 通過在 B 組件中 $emit, A 組件中 v-on 的方式實現(xiàn)數(shù)據(jù)傳遞

2、通過設(shè)置全局Vuex共享狀態(tài),通過 computed 計算屬性和 commit mutation的方式實現(xiàn)數(shù)據(jù)的獲取和更新,以達(dá)到父子組件通信的目的。

3、Vue Event Bus,使用Vue的實例,實現(xiàn)事件的監(jiān)聽和發(fā)布,實現(xiàn)組件之間的傳遞。

往往數(shù)據(jù)在不需要全局的情況而僅僅是父子組件通信時,使用第一種方式即可滿足。

A 組件與 C 組件之間的通信: (跨多級的組件嵌套關(guān)系)

如上圖,A 組件與 C 組件之間屬于跨多級的組件嵌套關(guān)系,以往兩者之間如需實現(xiàn)通信,往往通過以下方式實現(xiàn):

  1. 借助 B 組件的中轉(zhuǎn),從上到下props依次傳遞,從下至上,$emit事件的傳遞,達(dá)到跨級組件通信的效果
  2. 借助Vuex的全局狀態(tài)共享
  3. Vue Event Bus,使用Vue的實例,實現(xiàn)事件的監(jiān)聽和發(fā)布,實現(xiàn)組件之間的傳遞。

多級組件嵌套需要傳遞數(shù)據(jù)時,通常使用的方法是通過 vuex。但如果僅僅是傳遞數(shù)據(jù),而不做中間處理,使用 vuex 處理,未免有點大材小用。為此 Vue2.4 版本提供了另一種方法attrs/listeners

props/attrs/$listeners介紹

$props:當(dāng)前組件接收到的 props 對象,Vue 實例代理了對其 props 對象屬性的訪問。

**attrs:**包含了父作用域中不被 prop 所識別 (且獲取) 的特性綁定 (class 和 style 除外)。當(dāng)一個組件沒有聲明任何 prop 時,這里會包含所有父作用域的綁定 (class 和 style 除外),并且可以通過 v-bind="attrs" 傳入內(nèi)部組件。通常配合 interitAttrs 選項一起使用。 通俗的理解為:子輩可以通過$attrs將未在自己組件內(nèi)注冊的祖輩傳遞下來的參數(shù)接收來,并傳遞孫輩。

inheritAttrs:默認(rèn)情況下父作用域的不被認(rèn)作 props 的特性綁定 (attribute bindings) 將會“回退”且作為普通的 HTML 特性應(yīng)用在子組件的根元素上。通過設(shè)置 inheritAttrs 為 false,這些默認(rèn)行為將會被去掉。而通過實例屬性 $attrs 可以讓這些特性生效,且可以通過 v-bind 顯性的綁定到非根元素上。

**listeners:**包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監(jiān)聽器。它可以通過 v-on="listeners" 傳入內(nèi)部組件

代碼示例

A組件(index.vue)

<template>
    <div>
        <h1>我是parent</h1>
        <hr>
        <child1 
            :foo="foo" 
            :boo="boo" 
            :coo="coo" 
            :doo="doo" 
            title="跨級組件通信" 
            @test1="test1" 
            @test2="test2"
        ></child1>
    </div>
</template>
<script>
    const child1 = () => import("./child1.vue");
    export default {
        components: { child1 },
        data() {
            return {
                foo: "foo",
                boo: "boo",
                coo: "coo",
                doo: "doo"
            };
        },
        methods: {
            test1() {
                alert('test1')
            },
            test2() {
                alert('test2')
            }
        },
    };
</script>

B組件(child1.vue)

<template>
    <div>
        <h1>我是child1</h1>
        <p>props: {{ $props }}</p>
        <p>attrs: {{ $attrs }}</p>
        <button @click="toParent1()">觸發(fā)test1方法</button>
        <hr>
        <child2 v-bind="$attrs" v-on="$listeners"></child2>
    </div>
</template>
<script>
    const child2 = () => import("./child2.vue");
    export default {
        components: {
            child2
        },
        inheritAttrs: false, // 可以關(guān)閉自動掛載到組件根元素上的沒有在props聲明的屬性
        props: {
            foo: String // foo作為props屬性綁定
        },
        created() {
            console.log(this.$attrs); // { "boo": "boo", "coo": "coo", "doo": "doo", "title": "跨級組件通信" }
        },
        methods: {
            toParent1() {
                this.$emit('test1') // 執(zhí)行test1方法,alert彈框顯示'test1'
            }
        },
    };
</script>

C組件(child2.vue)

<template>
    <div class="border">
        <h1>我是child2</h1>
        <p>props: {{ $props }}</p>
        <p>attrs: {{ $attrs }}</p>
        <button @click="toParent2">觸發(fā)test2方法</button>
        <hr>
        <child3 v-bind="$attrs" v-on="$listeners"></child3>
    </div>
</template>
<script>
    const child3 = () => import("./child3.vue");
    export default {
        components: {
            child3
        },
        inheritAttrs: false,
        props: {
            boo: String
        },
        created() {
            console.log(this.$attrs); // { "coo": "coo", "doo": "doo", "title": "跨級組件通信" }
        },
        methods: {
            toParent2() {
                this.$emit('test2') // 執(zhí)行test2方法,alert彈框顯示'test2'
            }
        },
    };
</script>

D組件(child3.vue)

<template>
    <div class="border">
        <h1>我是child3</h1>
        <p>props: {{ $props }}</p>
        <p>attrs: {{ $attrs }}</p>
        <button @click="toParent1">觸發(fā)test1方法</button>
        <button @click="toParent2">觸發(fā)test2方法</button>
    </div>
</template>
<script>
    export default {
        props: {
            coo: String,
            title: String
        },
        methods: {
            toParent1() {
                this.$emit('test1') // 執(zhí)行test1方法,alert彈框顯示'test1'
            },
            toParent2() {
                this.$emit('test2') // 執(zhí)行test2方法,alert彈框顯示'test2'
            }
        },
    };
</script>

最終頁面展示效果

總結(jié):

A組件一共傳了四個變量foo、boo、coo、doo,一個屬性title,兩個接收函數(shù)test1,test2

組件B中props中接收了一個foo。所以$attrs傳的是剩下的 { "boo": "boo", "coo": "coo", "doo": "doo", "title": "跨級組件通信" }

組件C中props中接收了一個boo。所以$attrs傳的是剩下的 { "coo": "coo", "doo": "doo", "title": "跨級組件通信" }

組件D中props中接收了coo和title。所以$attrs傳的是剩下的 { "doo": "doo"}

文章每周持續(xù)更新,可以微信搜索「 前端大集錦 」第一時間閱讀,回復(fù)【視頻】【書籍】領(lǐng)取200G視頻資料和30本PDF書籍資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容