說說在 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; } } });
效果:

示例中有兩個按鈕,分別實現加 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 } });
效果:

我們使用 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 元件具有以下特徵:
$emit
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
只在元件渲染完成後才會被賦值,而且它是非響應式的。所以只有在萬不得已的情況下才使用它。
ofollow,noindex">本文示例程式碼
總結如下:
通訊方式 | 通訊方向 |
---|---|
props 【推薦】 |
父元件到子元件 |
自定義事件 $emit 【推薦】 |
子元件到父元件 |
中央事件匯流排【推薦】 | 元件間任意通訊 |
父子鏈 | 元件間任意通訊 |
子元件索引 | 父元件到子元件 |