Vue中組件間通訊的方式

Vue中組件間通訊的方式

Vue中組件間通訊包括父子組件、兄弟組件、隔代組件之間通訊。javascript

props $emit

這種組件通訊的方式是咱們運用的很是多的一種,props以單向數據流的形式能夠很好的完成父子組件的通訊,所謂單向數據流,就是數據只能經過props由父組件流向子組件,而子組件並不能經過修改props傳過來的數據修改父組件的相應狀態,全部的prop都使得其父子prop之間造成了一個單向下行綁定,父級prop的更新會向下流動到子組件中,可是反過來則不行,這樣會防止從子組件意外改變父級組件的狀態,致使難以理解數據的流向而提升了項目維護難度。實際上若是傳入一個基本數據類型給子組件,在子組件中修改這個值的話Vue中會出現警告,若是對於子組件傳入一個引用類型的對象的話,在子組件中修改是不會出現任何提示的,這兩種狀況都屬於改變了父子組件的單向數據流,是不符合可維護的設計方式的。
正由於這個特性,而咱們會有須要更改父組件值的需求,就有了對應的$emit,當咱們在組件上定義了自定義事件,事件就能夠由vm.$emit觸發,回調函數會接收全部傳入事件觸發函數的額外參數,$emit實際上就是是用來觸發當前實例上的事件,對此咱們能夠在父組件自定義一個處理接受變化狀態的邏輯,而後在子組件中如若相關的狀態改變時,就觸發父組件的邏輯處理事件。html

父組件向子組件傳值

父組件向子組件傳值經過prop傳遞值便可。vue

<!-- 子組件 -->
<template>
    <div>

        <div>我是子組件,接收:{{ msg }}</div>

    </div>
</template>

<script>
    export default {
        name: "child",
        components: {},
        props: ["msg"],
        data: () => ({
            
        }),
        beforeCreate: function() {},
        created: function() {},
        filters: {},
        computed: {},
        methods: {}
    }
</script>

<style scoped>
 
</style>
<!-- 父組件 -->
<template>
    <div>

        <child :msg="msg"></child>

    </div>
</template>

<script>
    import child from "./child";
    export default {
        components: { child },
        data: () => ({
            msg: "父組件 Msg"
        }),
        beforeCreate: function() {},
        created: function() {},
        filters: {},
        computed: {},
        methods: {}
    }
</script>

<style scoped>
    
</style>

子組件向父組件傳值

子組件向父組件傳值須要經過事件的觸發,將更改值的行爲傳遞到父組件去執行。java

<!-- 子組件 -->
<template>
    <div>

        <div>我是子組件,接收:{{ msg }}</div>
        <button @click="$emit('changeMsg', '子組件傳值 Msg')">觸發事件並傳遞值到父組件</button>

    </div>
</template>

<script>
    export default {
        name: "child",
        components: {},
        props: ["msg"],
        data: () => ({
            
        }),
        beforeCreate: function() {},
        created: function() {},
        filters: {},
        computed: {},
        methods: {}
    }
</script>

<style scoped>
 
</style>
<!-- 父組件 -->
<template>
    <div>

        <child 
            :msg="msg"
            @changeMsg="changeMsg"
        ></child>

    </div>
</template>

<script>
    import child from "./child";
    export default {
        components: { child },
        data: () => ({
            msg: "父組件 Msg"
        }),
        beforeCreate: function() {},
        created: function() {},
        filters: {},
        computed: {},
        methods: {
            changeMsg: function(msg){
                this.msg = msg;
            }
        }
    }
</script>

<style scoped>
    
</style>

v-model

v-model一般稱爲數據雙向綁定,也能夠稱得上是一種父子組件間傳值的方式,是當前組件與input等組件進行父子傳值,其本質上就是一種語法糖,經過props以及input(默認狀況下)的事件的event中攜帶的值完成,咱們能夠自行實現一個v-modelgit

<template>
    <div>

        <div>{{msg}}</div>
        <input :value="msg" @input="msg = $event.target.value">

    </div>
</template>

<script>
    export default {
        data: () => ({
            msg: "Msg"
        }),
        beforeCreate: function() {},
        created: function() {},
        filters: {},
        computed: {},
        methods: {}
    }
</script>

<style scoped>
    
</style>

sync修飾符

sync修飾符也能夠稱爲一個語法糖,在Vue 2.3以後新的.sync修飾符所實現的已經再也不像Vue 1.0那樣是真正的雙向綁定,而是和v-model相似,是一種語法糖的形式,也能夠稱爲一種縮寫的形式,在下面父組件兩種寫法是徹底等同的。github

<!-- 子組件 -->
<template>
    <div>

        <div>我是子組件,接收:{{ msg }}</div>
        <button @click="$emit('update:msg', '子組件傳值 Msg')">觸發事件並傳遞值到父組件</button>

    </div>
</template>

<script>
    export default {
        name: "child",
        components: {},
        props: ["msg"],
        data: () => ({
            
        }),
        beforeCreate: function() {},
        created: function() {},
        filters: {},
        computed: {},
        methods: {}
    }
</script>

<style scoped>
 
</style>
<!-- 父組件 -->
<template>
    <div>

        <child 
            :msg="msg1"
            @update:msg="msg1 = $event"
        ></child>
        
        <child
            :msg.sync="msg2"
        ></child>
        

    </div>
</template>

<script>
    import child from "./child";
    export default {
        components: { child },
        data: () => ({
            msg1: "父組件 Msg1",
            msg2: "父組件 Msg2",
        }),
        beforeCreate: function() {},
        created: function() {},
        filters: {},
        computed: {},
        methods: {
            changeMsg: function(msg){
                this.msg = msg;
            }
        }
    }
</script>

<style scoped>
    
</style>

provide inject

相似於ReactContext API,在父組件中經過provider來提供屬性,而後在子組件中經過inject來注入變量,不論子組件有多深,只要調用了inject那麼就能夠注入在provider中提供的數據,而不是侷限於只能從當前父組件的prop屬性來獲取數據,只要在父組件內的數據,子組件均可以調用。固然Vue中註明了provideinject主要在開發高階插件/組件庫時使用,並不推薦用於普通應用程序代碼中。vuex

<!-- 子組件 -->
<template>
    <div>
        <div>inject: {{msg}}</div>
    </div>
</template>

<script>
    export default {
        name: "child",
        inject: ["msg"],
        data: () => ({
            
        }),
        beforeCreate: function() {},
        created: function() {},
        filters: {},
        computed: {},
        methods: {}
    }
</script>

<style scoped>
 
</style>
<template>
    <div>

        <child></child>

    </div>
</template>

<script>
    import child from "./child";
    export default {
        components: { child },
        data: () => ({

        }),
        provide: {
            msg: "provide msg"
        },
        beforeCreate: function() {},
        created: function() {},
        filters: {},
        computed: {},
        methods: {}
    }
</script>

<style scoped>
    
</style>

$attrs $listeners

這種組件通訊的方式適合直接的父子組件,假設此時咱們有三個組件分別爲ABC,父組件A下面有子組件B,父組件B下面有子組件C,這時若是組件A直接想傳遞數據給組件C那就不能直接傳遞了,只能是組件A經過props將數據傳給組件B,而後組件B獲取到組件A傳遞過來的數據後再經過props將數據傳給組件C,固然這種方式是很是複雜的,無關組件中的邏輯業務增多了,代碼維護也沒變得困難,再加上若是嵌套的層級越多邏輯也複雜,無關代碼越多,針對這樣一個問題,Vue 2.4提供了$attrs$listeners來實現可以直接讓組件A直接傳遞消息給組件Csegmentfault

<!-- 子子組件 -->
<template>
    <div>

        

    </div>
</template>

<script>
    export default {
        name: "child-child",
        components: {},
        data: () => ({
            
        }),
        beforeCreate: function() {},
        created: function() {
            console.log(this.$attrs); // {param: 1, test: 2}
            console.log(this.$listeners); // {testEvent: ƒ}
        },
        filters: {},
        computed: {},
        methods: {}
    }
</script>

<style scoped>
 
</style>
<!-- 子組件 -->
<template>
    <div>
        <!-- 直接將剩餘的參數傳遞給子組件 -->
        <child-child v-bind="$attrs" v-on="$listeners"></child-child>
    </div>
</template>

<script>
    import childChild from "./child-child";
    export default {
        name: "child",
        components: { childChild },
        props: ["msg"], // 聲明瞭接收名爲msg的prop 此時在此組件的$attrs則不會再有msg參數
        data: () => ({
            
        }),
        inheritAttrs: false, // 默認設置爲true也可 // 默認狀況下true 父做用域的不被認做 props 的 attribute 綁定將會回退且做爲普通的 HTML attribute 應用在子組件的根元素上。
        beforeCreate: function() {},
        created: function() {
            console.log(this.$attrs); // {param: 1, test: 2}
            console.log(this.$listeners); // {testEvent: ƒ}
        },
        filters: {},
        computed: {},
        methods: {}
    }
</script>

<style scoped>
 
</style>
<!-- 父組件 -->
<template>
    <div>

        <child 
            :msg="msg"
            :param="1"
            :test="2"
            @testEvent="tips"
        ></child>
        
    </div>
</template>

<script>
    import child from "./child";
    export default {
        components: { child },
        data: () => ({
            msg: "Msg",
        }),
        beforeCreate: function() {},
        created: function() {},
        filters: {},
        computed: {},
        methods: {
            tips: function(...args){
                console.log(args);
            }
        }
    }
</script>

<style scoped>
    
</style>

$children $parent

這種方式就比較直觀了,直接操做父子組件的實例,$parent就是父組件的實例對象,而$children就是當前實例的直接子組件實例數組了,官方文檔的說明是子實例能夠用this.$parent訪問父實例,子實例被推入父實例的$children數組中,節制地使用$parent$children它們的主要目的是做爲訪問組件的應急方法,更推薦用propsevents實現父子組件通訊。此外在Vue2以後移除的$dispatch$broadcast也能夠經過$children$parent進行實現,固然不推薦這樣作,官方推薦的方式仍是更多簡明清晰的組件間通訊和更好的狀態管理方案如Vuex,實際上不少開源框架都仍是本身實現了這種組件通訊的方式,例如Mint UIElement UIiView等。數組

<!-- 子組件 -->
<template>
    <div>
        
    </div>
</template>

<script>
    export default {
        name: "child",
        data: () => ({
            
        }),
        beforeCreate: function() {},
        mounted: function() {
            console.log(this.$parent); // VueComponent {_uid: 2, ...}
            console.log(this.$children); // []
        },
        filters: {},
        computed: {},
        methods: {}
    }
</script>

<style scoped>
 
</style>
<!-- 父組件 -->
<template>
    <div>

        <child></child>

    </div>
</template>

<script>
    import child from "./child";
    export default {
        components: { child },
        data: () => ({

        }),
        beforeCreate: function() {},
        mounted: function() {
            console.log(this.$parent); // VueComponent {_uid: 1, ...}
            console.log(this.$children); // [VueComponent]
        },
        filters: {},
        computed: {},
        methods: {}
    }
</script>

<style scoped>
    
</style>

EventBus

在項目規模不大的狀況下,徹底可使用中央事件總線EventBus 的方式,EventBus能夠比較完美地解決包括父子組件、兄弟組件、隔代組件之間通訊,實際上就是一個觀察者模式,觀察者模式創建了一種對象與對象之間的依賴關係,一個對象發生改變時將自動通知其餘對象,其餘對象將相應作出反應。因此發生改變的對象稱爲觀察目標,而被通知的對象稱爲觀察者,一個觀察目標能夠對應多個觀察者,並且這些觀察者之間沒有相互聯繫,能夠根據須要增長和刪除觀察者,使得系統更易於擴展。首先咱們須要實現一個訂閱發佈類,並做爲全局對象掛載到Vue.prototype,做爲Vue實例中可調用的全局對象使用,此外務必注意在組件銷燬的時候卸載訂閱的事件調用,不然會形成內存泄漏。app

// 實現一個PubSub模塊
var PubSub = function() {
    this.handlers = {};
}

PubSub.prototype = {

    on: function(key, handler) { // 訂閱
        if (!(key in this.handlers)) this.handlers[key] = [];
        this.handlers[key].push(handler);
    },

    off: function(key, handler) { // 卸載
        const index = this.handlers[key].findIndex(item => item === handler);
        if (index < 0) return false;
        if (this.handlers[key].length === 1) delete this.handlers[key];
        else this.handlers[key].splice(index, 1);
        return true;
    },

    commit: function(key, ...args) { // 觸發
        if (!this.handlers[key]) return false;
        this.handlers[key].forEach(handler => handler.apply(this, args));
        return true;
    },

}

export { PubSub }
export default { PubSub }
<!-- 子組件 -->
<template>
    <div>

        <div>{{msg}}</div>
        <child></child>

    </div>
</template>

<script>
    import child from "./child";
    export default {
        components: { child },
        data: () => ({
            msg: "init"
        }),
        beforeCreate: function() {},
        created: function() {
            this.eventBus.on("ChangeMsg", this.changeMsg);
        },
        beforeDestroy: function(){
            this.eventBus.off("ChangeMsg", this.changeMsg);
        },
        filters: {},
        computed: {},
        methods: {
            changeMsg: function(msg){
                this.msg = msg;
            }
        }
    }
</script>

<style scoped>
    
</style>
<!-- 父組件 -->
<template>
    <div>

        <div>{{msg}}</div>
        <child></child>

    </div>
</template>

<script>
    import child from "./child";
    export default {
        components: { child },
        data: () => ({
            msg: "init"
        }),
        beforeCreate: function() {},
        created: function() {
            this.eventBus.on("ChangeMsg", this.changeMsg);
        },
        beforeDestroy: function(){
            this.eventBus.off("ChangeMsg", this.changeMsg);
        },
        filters: {},
        computed: {},
        methods: {
            changeMsg: function(msg){
                this.msg = msg;
            }
        }
    }
</script>

<style scoped>
    
</style>

Vuex

Vuex是一個專爲Vue.js應用程序開發的狀態管理模式,其採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
每個Vuex應用的核心就是store倉庫,store基本上就是一個容器,它包含着你的應用中大部分的狀態stateVuex和單純的全局對象有如下兩點不一樣:

  • Vuex的狀態存儲是響應式的,當Vue組件從store中讀取狀態的時候,若store中的狀態發生變化,那麼相應的組件也會相應地獲得高效更新。
  • 不能直接改變store中的狀態,改變store中的狀態的惟一途徑就是顯式地提交mutation,這樣使得咱們能夠方便地跟蹤每個狀態的變化。

實際上咱們能夠獲得更多使用Vuex的優勢:

  • 可使用時間旅行功能。
  • Vuex專作態管理,由一個統一的方法去修改數據,所有的修改都是能夠追溯的。
  • 在作日誌蒐集,埋點的時候,有Vuex更方便。
  • Vuex不會形成全局變量的污染,同時解決了父組件與孫組件,以及兄弟組件之間通訊的問題。

固然若是項目足夠小,使用Vuex多是繁瑣冗餘的。若是應用夠簡單,最好不要使用Vuex,上文中的一個簡單的store模式就足夠了。
在下面例子中,咱們經過提交mutation的方式,而非直接改變store.state.count,是由於咱們想要更明確地追蹤到狀態的變化。這個簡單的約定可以讓你的意圖更加明顯,這樣你在閱讀代碼的時候能更容易地解讀應用內部的狀態改變。此外這樣也讓咱們有機會去實現一些能記錄每次狀態改變,保存狀態快照的調試工具。

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment: function(state) {
            state.count++;
        }
    }
})

store.commit("increment");
console.log(store.state.count); // 1

因爲store中的狀態是響應式的,在組件中調用store中的狀態簡單到僅須要在計算屬性中返回便可。觸發變化也僅僅是在組件的methods中提交mutation便可。

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);


const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment: state => state.count++,
        decrement: state => state.count--
    }
})

new Vue({
    el: "#app",
    store,  
    computed: {
        count: function() {
            return this.$store.state.count;
        }
    },
     methods: {
        increment: function() {
            this.$store.commit("increment");
        },
        decrement: function() {
            this.$store.commit("decrement");
        }
    }
})

每日一題

https://github.com/WindrunnerMax/EveryDay

參考

https://zhuanlan.zhihu.com/p/109700915
https://juejin.cn/post/6844903887162310669
https://juejin.cn/post/6844903784963899405
https://segmentfault.com/a/1190000022083517
https://github.com/yangtao2o/learn/issues/97
https://github.com/YangYmimi/read-vue/issues/12
相關文章
相關標籤/搜索