Vue.js組件詳解之組件通信

我們已經知道,從父組件向子組件通信,通過props傳遞數據就可以了,但Vue組件通信的場景不止這一種,歸納起來,組件關係可分為父子通信、兄弟組件通信、跨級組件通信。以下會一一介紹各種組件的通信方法。

自定義事件

之前我總結過子組件向父組件通信是藉助於事件進行的,本次詳細說明一下。

當子組件需要向父組件傳遞數據時,就要用到自定義事件。v-on除了監聽DOM事件外,還可以用於組件之間的自定義事件。(這裡直接使用語法糖@)

如果你瞭解過JavaScript的設計模式——觀察者模式,一定知道dispatchEvent和addEventListener這兩個方法。Vue組件也有與之類似的一套模式,子組件用$emit()來觸發事件,父組件用$on()來監聽子組件的事件。

父組件也可以直接在子組件的自定義標籤上使用v-on來監聽子組件觸發的自定義事件,示例如下:

Vue.js組件詳解之組件通信

上面示例中,子組件有兩個按鈕,分別實現加1和減1的效果,在改變組件的data”counter”後,通過$emit()再把它傳遞給父組件,父組件用v-on:increase和v-on:reduce(示例中使用的語法糖@)。$emit()方法的第一個參數是自定義事件的名稱,例如示例的increase和reduce後面的參數都是要傳遞的數據,可以不填或填寫多個。

除了用v-on在組件上監聽自定義事件外,也可以監聽DOM事件,這時可以用.native修飾符表示監聽的是一個原生事件,監聽該組件的根元素,示例如下:

Vue.js組件詳解之組件通信

(試驗本例時請將e.target.value++的++去掉)在應用到本示例中是沒有意義的,不過注意父級的handleClick方法,裡面的e是獲取組件內觸發click事件的元素,我們可以在父級監聽子級的DOM事件,並獲取有價格的數據。(當然,自定義的事件裡面也可以傳當前的event/當前實例,在父級方法通過e.target獲取子級元素信息/e.counter獲取子級實例信息,可以自己做一下實例)

使用v-model

Vue 2.x可以在自定義組件上使用v-model指令,我們先來看一個示例:

Vue.js組件詳解之組件通信

仍然是點擊按鈕+的效果,不過這次組件$emit()的事件是特殊的input,在使用組件的父級,並沒有在上使用@input=”handler”,而是直接用了v-model綁定的一個數據total。這也可以稱作是一個語法糖(value+input構成的v-model需要了解的可以看一下使用render做的v-model效果),因為上面的示例可以間接地用自定義事件來實現:

Vue.js組件詳解之組件通信

v-model還可以用來創建自定義的表單輸入組件,進行數據雙向綁定,如下:

Vue.js組件詳解之組件通信

一定要理清v-model實現的原理(value+input的組合,value可以是自定義的任何值,input是input元素的事件)

上面示例實現的v-model雙向綁定組件,需要滿足下面兩個要求:

  1. 接收一個value屬性
  2. 在有新的value時觸發input事件

非父子組件通信

在實際業務中,除了父子組件通信外,還有很多非父子組件通信的場景,非父子組件一般有兩種:兄弟組件和跨多級組件。為了更加徹底的瞭解Vue 2.x中的通信方法。我們先來看一下在Vue 1.x是如何實現的,這樣便於我們瞭解Vue.js的設計思想。

在Vue 1.x中,除了$emit()方法外,還提供了$dispatch()和$broadcast()這兩種方法。$dispatch()用於向上級派發事件,只要是它的父級(一級或多級),都可以在vue實例的events選項內接收,示例如下:

Vue.js組件詳解之組件通信

同理,$broadcast()是由上級向下級廣播事件的,用法完全一致,只是方向相反。

這兩種方法一旦發出事件,任何組件都是可以接收到的,就近原則,而且會在第一次接收後停止冒泡,除非返回true

這兩個方法雖然看起來很好用,不過在vue 2.x卻都廢棄了,因為基於組件樹結構的事件流方式讓人難以理解,並且在組件結構擴展的過程中變得越來越脆弱,並且不能理解兄弟組件通信的問題。

在Vue 2.x中,推薦使用一個空的Vue實例作為中央事件總線(bus),也就是一箇中介。示例如下:

Vue.js組件詳解之組件通信

首先創建了一個名為bus的空Vue實例,裡面沒有任何內容;然後全局定義了組件component-a;最後創建Vue實例app,在app初始化時,也就是在生命週期mounted鉤子函數里監聽了來自bus的事件on-message,而在組件component-a中,點擊按鈕會通過bus把事件on-message發出去,此時app就會收到來自bus的事件,進而在回調函數中完成自己的業務邏輯。

這種方法巧妙而輕量地實現了任何組件間的通信,包括父子、兄弟、跨級。如果深入使用,可以擴展bus實例,給它添加data、methods、computed等選項,這些都是公用的,在業務中,尤其是協同開發時非常有用,因為經常共享一些通用的信息,比如:用戶登錄的暱稱、性別等,還有用戶的授權token等。只需在初始化時讓bus獲取一次。任何時間、任何地點(組件)都可以從中直接使用。

除了中央事件總線bus外,還有兩種方法可以實現組件間通信:父鏈和子組件索引。

父鏈

在子組件中,使用this.$parent可以直接訪問該組件的父實例或組件,父組件也可以通過this.$children訪問所有的子組件,而且可以遞歸向上或向下無限訪問,直到根實例或最內的組件。實例如下:

Vue.js組件詳解之組件通信

儘管Vue允許這樣操作,但在業務中,子組件應該儘可能地避免依賴父組件的數據,更不應該去主動的修改它的數據,因為這樣使得父子組件緊耦合,只看父組件,很難理解它的狀態,因為它可能被任意組件修改,理想狀態下,只有組件自己能修改它的狀態。父子通信最好是通過props和$emit來通信。

子組件索引

當子組件較多時,通過this.$children來一一遍歷我們需要的一個組件實例是比較困難的,尤其是組件動態渲染時,它們的序列是不固定的。Vue提供了子組件索引的方法,用特殊的屬性ref來為子組件指定一個索引名稱,示例代碼如下:

Vue.js組件詳解之組件通信

在父組件模板中,子組件標籤上使用ref指定一個名稱,並在父組件內通過this.$refs來訪問指定名稱的子組件。

注意:$refs只在組件渲染完成後才填充,並且它是非響應式的。它僅僅作為一個直接訪問子組件的應急方案,應當避免在模板或計算屬性中使用$refs。


分享到:


相關文章: