說說在 Vue.js 中如何實現組件間通訊(高級篇)

以前說過,能夠使用 props 將數據從父組件傳遞給子組件。其實還有其它種的通訊方式,下面咱們一一娓娓道來。html

1 自定義事件

經過自定義事件,咱們能夠把數據從子組件傳輸回父組件。子組件經過 $emit() 來觸發事件,而父組件經過 $on() 來監聽事件,這是典型的觀察者模式。vue

html:vuex

<div id="app">
    <p>總數:{{total}}</p>
    <deniro-component @increase="setTotal"
                      @reduce="setTotal"
    ></deniro-component>
</div>
複製代碼

js:bash

Vue.component('deniro-component', {
	template: '\ <div>\ <button @click="increase">+1</button>\ <button @click="reduce">-1</button>\ </div>',
	data: function () {
		return {
			counter: 0
		}
	},
	methods: {
		increase: function () {
			this.counter++;
			this.$emit('increase', this.counter);
		},
		reduce: function () {
			this.counter--;
			this.$emit('reduce', this.counter);
		}
	}
});

var app = new Vue({
	el: '#app',
	data: {
		total: 0
	},
	methods: {
		setTotal: function (total) {
			this.total = total;
		}
	}
});
複製代碼

效果: app

示例中有兩個按鈕,分別實現加 1 與減 1 操做。點擊按鈕後,執行組件中定義的 increase 或 reduce 方法,在方法內部,使用 $emit 把值傳遞迴父組件。 $emit 方法的第一個參數是使用組件時定義的事件名,示例中是 @increase@reduce函數

<deniro-component @increase="setTotal"
				  @reduce="setTotal"
></deniro-component>
複製代碼

這兩個事件又綁定了 setTotal 方法,該方法修改了 total 值。 $emit 方法的其它參數是須要回傳給父組件的參數。ui

也能夠使用 v-on.native 來監聽原生事件,好比這裏監聽組件的點擊事件:this

html:spa

<div id="app">
    ...
    <deniro-component ...
                      @click.native="click"
    ></deniro-component>
</div>
複製代碼

js:.net

...
var app = new Vue({
	el: '#app',
	data: {
		total: 0
	},
	methods: {
		...
		click: function () {
			console.log("原生點擊事件");
		}
	}
});
複製代碼

這樣,點擊按鈕後,就能夠捕獲原生的點擊事件啦O(∩_∩)O~

**注意:**這裏監聽的是這個組件根元素的原生點擊事件。

2 v-model 方式

也能夠使用 v-model 方式來直接綁定父組件變量,把數據從子組件傳回父組件。

html:

<div id="app2">
    <p>總數:{{total}}</p>
    <deniro-component2 v-model="total"
    ></deniro-component2>
</div>
複製代碼

js:

Vue.component('deniro-component2', {
	template: '\ <div>\ <button @click="click">+1</button>\ </div>',
	data: function () {
		return {
			counter: 0
		}
	},
	methods: {
		click: function () {
			this.counter++;
			this.$emit('input', this.counter);
		}
	}
});

var app2 = new Vue({
	el: '#app2',
	data: {
		total: 0
	}
});
複製代碼

效果:

咱們使用 v-model="total" 直接綁定變量 total。接着在子組件中,在 $emit 方法傳入事件名 input,這樣 Vue.js 就會自動找到 `v-model 綁定的變量啦O(∩_∩)O~

咱們也能夠使用自定義事件來實現上述示例——

html:

<div id="app3">
    <p>總數:{{total}}</p>
    <deniro-component3 @input="setTotal"
    ></deniro-component3>
</div>

複製代碼

js:

Vue.component('deniro-component3', {
	template: '\ <div>\ <button @click="click">+1</button>\ </div>',
	data: function () {
		return {
			counter: 0
		}
	},
	methods: {
		click: function () {
			this.counter++;
			this.$emit('input', this.counter);
		}
	}
});

var app3 = new Vue({
	el: '#app3',
	data: {
		total: 0
	},
	methods: {
		setTotal: function (total) {
			this.total = total;
		}
	}
});
複製代碼

效果與上例相同。

咱們還能夠在自定義的表單輸入組件中利用 v-model,實現數據雙向綁定:

html:

<div id="app4">
    <p>總數:{{total}}</p>
    <deniro-component4 v-model="total"
    ></deniro-component4>
    <button @click="increase">+1</button>
</div>
複製代碼

js:

Vue.component('deniro-component4', {
	props: ['value'],
	template: '<input :value="value" @input="update">+1</input>',
	data: function () {
		return {
			counter: 0
		}
	},
	methods: {
		update: function (event) {
			this.$emit('input', event.target.value);
		}
	}
});

var app4 = new Vue({
	el: '#app4',
	data: {
		total: 0
	},
	methods: {
		increase: function () {
			this.total++;
		}
	}
});
複製代碼

效果:

這裏咱們首先利用 v-model,在自定義組件中綁定了 total 變量。而後在組件內部,定義了 props 爲 ['value']注意這裏必須爲 value,才能接收綁定的 total 變量。接着在組件模板中把接收到的 value 值(即 total 變量值),做爲 <input> 元素的初始值,並綁定 input 事件。下一步,在 input 事件中,經過 this.$emit('input', event.target.value) 把 total 值傳回父組件的 <button @click="increase">+1</button>。最後在 increase 方法中,遞增 total 值。

這個示例,咱們綜合使用了 props 、v-model和自定義事件,實現了數據的雙向綁定。

總的來講,一個具備雙向綁定的 v-model 組件具備如下特徵:

  1. 使用 props 接收父組件的 value。
  2. 子組件中擁有能夠更新 value 的 HTML 元素,當更新 value 時,觸發 input 事件。事件內部使用 $emit 將新的 value 值回傳給父組件。

3 非父子組件

非父子組件指的是兄弟組件或者跨多級組件。

3.1 中央事件總線

咱們能夠建立一個空的 Vue 實例做爲中央事件總線,實現非父子組件之間的通訊。

html:

<div id="app5">
    <p>監聽子組件消息:{{message}}</p>
    <deniro-component5></deniro-component5>
</div>
複製代碼

js:

var bus = new Vue();
Vue.component('deniro-component5', {
	template: '<button @click="sendMessage">發送消息</button>',
	methods: {
		sendMessage: function () {
			bus.$emit('on-message', '來自於 deniro-component5 的消息');
		}
	}
});
var app5 = new Vue({
	el: "#app5",
	data: {
		message: ''
	},
	mounted: function () {
		var that = this;

		bus.$on('on-message', function (message) {
			that.message = message;
		})
	}
});
複製代碼

注意: 由於 bus.$on() 中的函數,this 指向的是自己,因此咱們必須在外層定義一個 that,讓它引用 mounted 對象。

效果:

首先建立了一個空的 Vue 實例做爲中央事件總線。而後在定義的子組件綁定的 click 事件中,經過 bus.$emit() 發送消息。接着在初始化 app 實例的 mounted 函數時,使用 bus.$on() 方法監聽消息。

這種方式能夠實現組件間任意通訊。咱們還能夠擴展 bus 實例,爲它添加 data、methods、computed 等屬性,這些都是公共屬性,能夠共用。因此在此能夠放置須要共享的信息,好比用戶登錄暱稱等。使用時只須要初始化一次 bus 便可,因此在單頁面富客戶端中應用普遍。

若是項目較大,那麼能夠使用具備狀態管理的 vuex 哦O(∩_∩)O~

3.2 父子鏈

子組件能夠使用 this.$parent 來訪問父組件實例;而父組件能夠使用 this.$children 來訪問它的全部子組件實例。這些方法能夠遞歸向上或向下,直到根實例或者葉子實例。

html:

<div id="app6">
    <p>消息:{{message}}</p>
    <deniro-component6></deniro-component6>
</div>
複製代碼

js:

Vue.component('deniro-component6', {
	template: '<button @click="sendMessage">發送消息</button>',
	methods: {
		sendMessage: function () {
			//經過父鏈找到父組件,修改相應的變量
			this.$parent.message='來自於 deniro-component6 的消息';
		}
	}
});
var app6 = new Vue({
	el: "#app6",
	data: {
		message: ''
	}
});
複製代碼

效果:

**注意:**只有在萬不得已的狀況下,才使用父子鏈,實現組件間任意通訊。由於這樣作,會讓兩個組件之間緊耦合,代碼變得難理解與維護。若是隻是父子組件之間的通訊,儘可能採用 props 與自定義事件 $emit 來實現。

3.3 子組件索引

若是一個組件的子組件較多且是動態渲染的場景,使用 this.$children 來遍歷這些子組件較麻煩。這時就能夠使用 ref 來爲子組件指定索引名稱,方便後續查找。

html:

<div id="app7">
    <button @click="getChild">獲取子組件實例</button>
    <deniro-component7 ref="child"></deniro-component7>
</div>
複製代碼

js:

Vue.component('deniro-component7', {
	template: '<div>deniro-component7</div>',
	data: function () {
		return {
			message: '登錄不到兩週,InSight探測器意外捕捉到火星的風聲'
		}
	}
});
var app7 = new Vue({
	el: "#app7",
	methods: {
		getChild: function () {
			//使用 $refs 來訪問組件實例
			console.log(this.$refs.child.message);
		}
	}
});
複製代碼

輸出結果:

登錄不到兩週,InSight探測器意外捕捉到火星的風聲

注意:$refs 只在組件渲染完成後纔會被賦值,並且它是非響應式的。因此只有在萬不得已的狀況下才使用它。

本文示例代碼


總結以下:

通訊方式 通訊方向
props【推薦】 父組件到子組件
自定義事件 $emit【推薦】 子組件到父組件
中央事件總線【推薦】 組件間任意通訊
父子鏈 組件間任意通訊
子組件索引 父組件到子組件
相關文章
相關標籤/搜索