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

deniro發表於2018-12-09

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

之前說過,可以使用 props 將資料從父元件傳遞給子元件。其實還有其它種的通訊方式,下面我們一一娓娓道來。

1 自定義事件

通過自定義事件,我們可以把資料從子元件傳輸回父元件。子元件通過 $emit() 來觸發事件,而父元件通過 $on() 來監聽事件,這是典型的觀察者模式。

html:

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

js:

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;
		}
	}
});
複製程式碼

效果:

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

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

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

這兩個事件又繫結了 setTotal 方法,該方法修改了 total 值。 $emit 方法的其它引數是需要回傳給父元件的引數。

也可以使用 v-on.native 來監聽原生事件,比如這裡監聽元件的點選事件:

html:

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

js:

...
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
	}
});
複製程式碼

效果:

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

我們使用 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++;
		}
	}
});
複製程式碼

效果:

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

這裡我們首先利用 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.js 中如何實現元件間通訊(高階篇)

首先建立了一個空的 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: ''
	}
});
複製程式碼

效果:

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

**注意:**只有在萬不得已的情況下,才使用父子鏈,實現元件間任意通訊。因為這樣做,會讓兩個元件之間緊耦合,程式碼變得難理解與維護。如果只是父子元件之間的通訊,儘量採用 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【推薦】 子元件到父元件
中央事件匯流排【推薦】 元件間任意通訊
父子鏈 元件間任意通訊
子元件索引 父元件到子元件

相關文章