Vue 組件的通訊方式都有哪些?

  • 2021 年 2 月 18 日
  • 筆記

說一下 Vue 組件的通訊方式都有哪些?(父子組件,兄弟組件,多級嵌套組件等等)

  • 一、父組件向子組件傳值
  • 二、子組件向父組件傳值
  • 三、兄弟組件傳值
  • 四、跨組件

 

一、父組件向子組件傳值

1.1props方式:可以是數組或對象,用於接收父組件的數據

<div id="app">
    <child-component :msg="message" :count="count"></child-component>
    <button @click="count++">
            點擊+1
    </button>
</div>
<script>
    const childComponent = {
        props: {
            msg: String
            count: Number
        },
        template: `<div><p>{{msg}}</p><p>{{count}}</p><div>`
    }
    new Vue({
        el: '#app',
        data: {
            message:'父組件的值',
            count:0
        },
        components:{
            childComponent
        }
    })
</script>

1.2 通過$parent獲取父組件實例的方法或者屬性

這種方式,從嚴格意義上講不是值的傳遞,而是一種 「取」 (不推薦直接通過實例進行值的獲取)

實例屬性$parent可以獲得父組件的實例,藉助實例可以調用父實例中的方法,或者父實例上的屬性

<div id="app">
    <child-component :msg="message" :count="count"></child-component>
    <button @click="count++">
        點擊+1
    </button>
</div>
<script>
    //子組件
    const childComponent = {
        data:() => ({
            msg:'',
            count:null
        }),
        methods: {
            handleClick(){
                //調用父級實例的方法
                this.$parent.parentMethods();
            }
        }
        mounted () {
            // 獲取父級實例中的屬性
            this.msg = this.$parent.message;
            this.count = this.$parent.count;
        }
        template: `<div><p @click="handleClick">{{msg}}</p><p>{{count}}</p><div>`
    }
    // 父級
    new Vue({
        el: '#app',
        data: {
            message: '父組件的值',
            count: 0
        },
        methods: {
            parentMethod () {
                console.log('我是父級的方法');
            }
        }
        components: {
            childComponent
        }
    })
</script>

1.3 使用修飾符.sync

修飾符.sync2.3.0+新增,它對props起到了一種修飾的作用,使用.sync進行修飾的props,意味著子組件有修改它的意圖,這種情況下它只起到一個標註性作用,有它沒它都不會影響邏輯

使用.sync修改上邊的程式碼:

// 父組件 List.vue
<template>
    <!-- 這裡不寫 .sync 也不會影響結果 -->
    <List-item :title.sync="title" @update:title="updataTitle"></List-item>
</template>
<script>
    import ListItem from "./ListItem";
    export default {
        data() {
            return {
                title: "我是title",
            }
        },
        components: {
            ListItem
        },
        methods: {
            updataTitle(res) {
                this.title = res;
            }
        }
    }
</script>

// 子組件 ListItem.vue
<template>
    <div>
        <button @click="handleClick">Click me</button>
        <div>{{title}}</div>
    </div>
</template>
<script>
    export default {
        props: {
            title: String, 
        },
        methods: {
            handleClick() {
                // 子組件向父組件傳值
                this.$emit('update:title', '我要父組件更新 title');
            }
        }
    }
</script>

使用.sync向子組件傳遞多個props

當我們用一個對象同時設置多個prop的時候,也可以將這個.sync修飾符和v-bind配合使用:

<text-document v-bind.sync="doc"></text-document>

這樣會把doc對象中的每一個屬性(如:title)都作為一個獨立的prop傳進去,然後各自添加用於更新的v-on監聽器。

二、子組件向父組件傳值

2.1 通過事件傳值$emit

使用:

子組件使用$emit發送一個自定義事件

父組件使用指令v-on監聽子組件發送的事件

<div id="app">
    <child-component @child-event="childEvent"></child-component>
</div>
<script>
    //子組件
    const childComponent={
        data:()=>({
            msg:'點擊發送值到父組件',
            count:null
        }),
        methods:{
            handleClick(data){
                this.$emit('child-event','我是子組件傳過來的值');
            }
        }
        template:`<div><p @click="handleClick">{{msg}}</p><p>{{count}}</p><div>`
    }
    // 父級
    new Vue({
        el:'#app',
        data:{
            message:'父組件的值',
            count:0
        },
        methods:{
            childEvent(data){
                console.log("子組件傳過來的值",data);
            }
        }
        components:{
            childComponent
        }
	})
</script>

2.2 通過$children獲取子組件實例

$parent

2.3 通過ref註冊子組件引用

雖然存在prop和事件,但是有時仍可能需要在 JavaScript 里直接訪問一個子組件。為了實現這樣的需求,可以使用ref特性為某個子組件設置一個 ID 引用,就是一個身份標識

<div id="app">
    <child-component ref="childComponent"></child-component>
    <button @click="getRefs">
        獲取子組件實例
    </button>
</div>
<script>
    //子組件
    const childComponent={
        data:()=>({
            msg:'點擊發送值到父組件',
            count:null
        }),
        template:`<div><p @click="handleClick">{{msg}}</p><p>{{count}}</p><div>`
    }
    // 父級
    new Vue({
        el:'#app',
        data:{
            message:'父組件的值',
            count:0
        },
        methods:{
            getRefs(){
                console.log("子組件傳過來的值",this.$refs.childComponent.msg);
            }
        }
        components:{
            childComponent
        }
	})
</script>

三、兄弟組件傳值

3.1Bus中央事件匯流排

非父子組件傳值,可以使用一個空的Vue實例作為中央事件匯流排,結合實例方法$on$emit使用

注意:

註冊的Bus要在組件銷毀時卸載,否則會多次掛載,造成觸發一次但多個響應的情況。

beforeDestroy(){
    this.$Bus.$off('方法名',value);
}

Bus定義方式:

  • 1,將Bus抽離出來,組件有需要時引入

    // bus.js
    import Vue from 'vue';
    const Bus = new Vue();
    export default Bus;
    
  • 2,將Bus掛載到 Vue 根實例的原型上

    import Vue from 'vue';
    Vue.prototype.$bus = new Vue();
    
  • 3,將Bus注入到 Vue 根對象上

    import Vue form 'vue';
    const Bus = new Vue();
    new Vue({
        el:'#app',
        data:{
            Bus
        }
    })
    

    使用例子:

    <div id="app">
        <child-component ></child-component>
        <child-component-two ></child-component-two>
    </div>
    <script>
        Vue.prototype.$bus = new Vue();
        //子組件1 
        const childComponent={
            data:()=>({
                msg:'我是子組件一',
                sendMsg:'我是子組件一發送的值'
            }),
            methods:{
                handleClick(){
                    this.$Bus.$emit('sendMsg',this.sendMsg);
                }
            }
            template:`<div><p @click="handleClick">{{msg}}</p></p><div>`
        }
        //子組件2
        const childComponentTwo={
            data:()=>({
                msg:'我是子組件二',
                brotherMsg:''
            }),
            mounted(){
                this.$Bus.$on('sendMsg',data=>{
                    this.brotherMsg = data;
                })
            },
            beforeDestroy(){
                this.$Bus.$off('sendMsg');
            }
            template:`<div><p @click="handleClick">{{msg}}</p><p>{{brotherMsg}}</p><div>`
        }
    
        // 父級
        new Vue({
            el:'#app',
            data:{},
            components:{
                childComponent,
                childComponentTwo
            }
        })
    </script>
    

四、跨組件

4.1$attrs$listeners

如果父組件 A 下面有子組件 B,組件 B 下面有組件 C,這時如果組件 A 直接想傳遞數據給 組件 C,那就行不通了!所以,這時可以使用$attrs$listeners

Vue 2.4 提供了$attrs$listeners來實現能夠直接讓 組件 A 傳遞消息給 組件 C

// 組件A
Vue.component('A', {
    template: `
        <div>
            <p>this is parent component!</p>
            <B :messagec="messagec" :message="message" v-on:getCData="getCData" v-on:getChildData="getChildData(message)"></B>
        </div>
    `,
    data() {
        return {
            message: 'hello',
            messagec: 'hello c' //傳遞給c組件的數據
		}		
	},
    methods: {
        // 執行B子組件觸發的事件
        getChildData(val) {
       		console.log(`這是來自B組件的數據:${val}`);
        },

        // 執行C子組件觸發的事件
        getCData(val) {
        	console.log(`這是來自C組件的數據:${val}`);
        }
	}
});

// 組件B
Vue.component('B', {
    template: `
        <div>
            <input type="text" v-model="mymessage" @input="passData(mymessage)"> 
            <!-- C組件中能直接觸發 getCData 的原因在於:B組件調用 C組件時,使用 v-on 綁定了 $listeners 屬性 -->
            <!-- 通過v-bind 綁定 $attrs 屬性,C組件可以直接獲取到 A組件中傳遞下來的 props(除了 B組件中 props聲明的) -->
            <C v-bind="$attrs" v-on="$listeners"></C>
        </div>
    `,
    /**
    * 得到父組件傳遞過來的數據
    * 這裡的定義最好是寫成數據校驗的形式,免得得到的數據是我們意料之外的
    *
    * props: {
    *   message: {
    *     type: String,
    *     default: ''
    *   }
    * }
    *
    */
	props: ['message'],
	data () {
        return {
            mymessage: this.message
        }
	},
    methods: {
        passData(val){
            //觸發父組件中的事件
            this.$emit('getChildData', val)
        }
    }
});

// 組件C
Vue.component('C', {
    template: `
        <div>
            <input type="text" v-model="$attrs.messagec" @input="passCData($attrs.messagec)">
        </div>
    `,
    methods: {
        passCData(val) {
            // 觸發父組件A中的事件
            this.$emit('getCData',val)
        }
    }
});

var app = new Vue({
    el:'#app',
    template: `
        <div>
            <A />
        </div>
    `
});

4.2provideinject

熟悉 React 開發的同學對 Context API 肯定不會陌生吧!在 Vue 中也提供了類似的 API 用於組件之間的通訊。在父組件中通過provide來提供屬性,然後在子組件中通過inject來注入變數。不論子組件有多深,只要調用了inject,那麼就可以注入在provide中提供的數據,而不是局限於只能從當前父組件的prop屬性來獲取數據,只要在父組件的生命周期內,子組件都可以調用。這和 React 中的 Context API 有沒有很相似!

// 定義 parent 組件
Vue.component('parent', {
    template: `
        <div>
            <p>this is parent component!</p>
            <child></child>
        </div>
    `,
    provide: {
        for:'test'
    },
    data() {
        return {
            message: 'hello'
        }
    }
});

// 定義 child 組件
Vue.component('child', {
    template: `
        <div>
        	<input type="tet" v-model="mymessage"> 
        </div>
    `,
    inject: ['for'],    // 得到父組件傳遞過來的數據
    data(){
        return {
            mymessage: this.for
        }
    },
});

const app = new Vue({
    el: '#app',
    template: `
        <div>
        	<parent />
        </div>
    `
});

上面的實例中,定義了組件parent和組件child,組件parent和 組件child是父子關係。

  • parent組件中,通過provide屬性,以對象的形式向子孫組件暴露了一些屬性
  • child組件中,通過inject屬性注入了parent組件提供的數據,實際這些通過inject注入的屬性是掛載到 Vue 實例上的,所以在組件內部可以通過this來訪問

注意:

官網文檔提及 provide 和 inject 主要為高階插件 / 組件庫提供用例,並不推薦直接用於應用程式程式碼中。

4.3 Vuex 狀態管理

Vuex 是狀態管理工具,實現了項目狀態的集中式管理。工具的實現借鑒了 Flux、Redux 和 The Elm Architecture 的模式和概念。當然與其他模式不同的是,Vuex 是專門為 Vue.js 設計的狀態管理庫,以利用 Vue.js 的細粒度數據響應機制來進行高效的狀態更新。