1. 程式人生 > 程式設計 >深入瞭解Vue元件七種通訊方式

深入瞭解Vue元件七種通訊方式

目錄
  • 1. props/$emit
    • 簡介
    • 程式碼例項
  • 2. v-slot
    • 簡介
    • 程式碼例項
  • 3. $refs/ $parent/ $children/$root
    • 簡介
    • 程式碼例項
  • 4. $attrs/$listener
    • 簡介
    • 程式碼例項
  • 5. provide/inject
    • 簡介
    • 程式碼例項
  • 6. eventBus
    • 簡介
    • 原理分析
    • 程式碼例項
  • 7. x
    • 程式碼例項
  • 總結

    vue元件通訊的方式,這是在面試中一個非常高頻的問題,我剛開始找實習便經常遇到這個問題,當時只知道回到props和 $emit,後來隨著學習的深入,才發現vue元件的通訊方式竟然有這麼多!

    今天對vue元件通訊方式進行一下總結,如寫的有疏漏之處還請大家留言。

    1. props/$emit

    簡介

    props和 $emit相信大家十分的熟悉了,這是我們最常用的vue通訊方式。

    props:props可以是陣列或物件,用於接收來自父元件通過v-bind傳遞的資料。當props為陣列時,直接接收父元件傳遞的屬性;當 props 為物件時,可以通過type、default、required、validator等配置來設定屬性的型別、預設值、是否必傳和校驗規則。

    $emit:在父子元件通訊時,我們通常會使用 $emit來觸發父元件v-on在子元件上繫結相應事件的監聽。

    程式碼例項

    下面通過程式碼來實現一下props和 $emit的父子元件通訊,在這個例項中,我們都實現了以下的通訊:

    父向子傳值:父元件通過 :messageFromParent="message" 將父元件 message 值傳遞給子元件,當父元件的 input 標籤輸入時,子元件p標籤中的內容就會相應改變。

    子向父傳值:父元件通過 @on-receive="receive" 在子元件上綁定了 receive 事件的監聽,子元件 input 標籤輸入時,會觸發 receive 回撥函式, 通過 this.$emit('on-receive',this.message) 將子元件 message 的值賦值給父元件 messageFromChild ,改變父元件p標籤的內容。

    請看程式碼:

    // 子元件程式碼
    <template>
      <div class="child">
        <h4>this is child component</h4>
        <input type="text" v-model="message" @keyup="send" />
        <p>收到來自父元件的訊息:{{ messageFromParent }}</p>
      </div>
    </template>
    <script>
    export default {
      name: 'Child',props: ['messageFromParent'],// 通過props接收父元件傳過來的訊息
      data() {
        return {
          message: '',}
      },methods: {
        send() {
          this.$emit('on-receive',this.message)  // 通過 $emit觸發on-receive事件,呼叫父元件中receive回撥,並將this.message作為引數
        },},}
    </script>
    // 父元件程式碼
    <template>
      <div class="parent">
        <h3>this is parent component</h3>
        <input type="text" v-model="message" />
        <p>收到來自子元件的訊息:{{ messageFromChild }}</p>
        <Child :messageFromParent="message" @on-receive="receive" />
      </div>
    </template>
    <script>
    import Child from './child'
    export default {
      name: 'Parent',data() {
        return {
          message: '',// 傳遞給子元件的訊息
          messageFromChild: '',components: {
        Child,methods: {
        receive(msg) { // 接受子元件的資訊,並將其賦值給messageFromChild
          this.messageFromChild = msg
        },}
    </script>

    效果預覽

    深入瞭解Vue元件七種通訊方式

    2. v-slot

    簡介

    v-slot是 Vue2.6 版本中新增的用於統一實現插槽和具名插槽的api,用於替代 slot(2.6.0廢棄) 、 slot-scope(2.6.0廢棄) 、 scope(2.5.0廢棄) 等api。

    v-slot在 template 標籤中用於提供具名插槽或需要接收 prop 的插槽,如果不指定 v-slot ,則取預設值 default 。

    程式碼例項

    下面請看v-slot的程式碼例項,在這個例項中我們實現了:

    父向子傳值:父元件通過 <template v-slot:child>{{ message }}</template> 將父元件的message值傳遞給子元件,子元件通過 <slot name="child"></slot> 接收到相應內容,實現了父向子傳值。

    // 子元件程式碼
    <template>
      <div class="child">
        <h4>this is child component</h4>
        <p>收到來自父元件的訊息:
          <slot name="child"></slot>  <!--展示父元件通過插槽傳遞的{{message}}-->
        </p>
      </div>
    </template>
    <template>
      <div class="parent">
        <h3>this is parent component</h3>
        <input type="text" v-model="message" />
        <Child>
          <template v-slot:child>
            {{ message }}  <!--插槽要展示的內容-->
          </template>
        </Child>
      </div>
    </template>
    <script>
    import Child from './child'
    export default {
      name: 'Parent',data() {
        return {
          message: '',}
    </script>

    效果預覽

    深入瞭解Vue元件七種通訊方式

    3. $refs/ $parent/ $children/$root

    簡介

    我們也同樣可以通過 $refs/$parent/$children/$root 等方式獲取 Vue 元件例項,得到例項上繫結的屬性及方法等,來實現元件之間的通訊。

    $refs:我們通常會將 $refs繫結在DOM元素上,來獲取DOM元素的 attributes。在實現元件通訊上,我們也可以將 $refs 繫結在子元件上,從而獲取子元件例項。

    $parent:我們可以在 Vue 中直接通過 this.$parent 來獲取當前元件的父元件例項(如果有的話)。

    $children:同理,我們也可以在 Vue 中直接通過 this.$children 來獲取當前元件的子元件例項的陣列。但是需要注意的是, this.$children 陣列中的元素下標並不一定對用父元件引用的子元件的順序,例如有非同步載入的子元件,可能影響其在 children 陣列中的順序。所以使用時需要根據一定的條件例如子元件的name去找到相應的子元件。

    $root:獲取當前元件樹的根 Vue 例項。如果當前例項沒有父例項,此例項將會是其自己。通過 $root ,我們可以實現元件之間的跨級通訊。

    程式碼例項

    下面來看一個 $ parent 和 $ children 使用的例項(由於這幾個api的使用方式大同小異,所以關於 $ refs 和 $ root 的使用就不在這裡展開了,在這個例項中實現了:

    父向子傳值:子元件通過 $parent.message 獲取到父元件中message的值。

    子向父傳值:父元件通過 $children 獲取子元件例項的陣列,在通過對陣列進行遍歷,通過例項的 name 獲取到對應 Child1 子元件例項將其賦值給 child1,然後通過 child1.message 獲取到 Child1 子元件的message。

    程式碼如下:

    // 子元件
    <template>
      <div class="child">
        <h4>this is child component</h4>
        <input type="text" v-model="message" />
        <p>收到來自父元件的訊息:{{ $parent.message }}</p>  <!--展示父元件例項的message-->
      </div>
    </template>
    <script>
    export default {
      name: 'Child1',// 父元件通過this.$children可以獲取子元件例項的message
        }
      },}
    </script>
    // 父元件
    <template>
      <div class="parent">
        <h3>this is parent component</h3>
        <input type="text" v-model="message" />
        <p>收到來自子元件的訊息:{{ child1.message }}</p> <!--展示子元件例項的message-->
        <Child />
      </div>
    </template>
    <script>
    import Child from './child'
    export default {
      name: 'Parent',child1: {},mounted() {
        this.child1 = this.$children.find((child) => {
          return child.$options.name === 'Child1'  // 通過options.name獲取對應name的child例項
        })
      },}
    </script>

    效果預覽

    深入瞭解Vue元件七種通訊方式

    4. $attrs/$listener

    簡介

    $ attrs和 $ listeners 都是 Vue2.4 中新增加的屬性,主要是用來供使用者用來開發高階元件的。

    $attrs:用來接收父作用域中不作為 prop 被識別的 attribute 屬性,並且可以通過 v-bind="$attrs" 傳入內部元件——在建立高級別的元件時非常有用。

    試想一下,當你建立了一個元件,你要接收 param1 、param2、param3 …… 等數十個引數,如果通過 props,那你需要通過 props: ['param1','param2','param3',……] 等宣告一大堆。如果這些 props 還有一些需要往更深層次的子元件傳遞,那將會更加麻煩。

    而使用 $attrs ,你不需要任何宣告,直接通過 $attrs.param1 、 $attrs.param2 ……就可以使用,而且向深層子元件傳遞上面也給了示例,十分方便。

    $listeners:包含了父作用域中的 v-on 事件監聽器。它可以通過 v-on="$listeners" 傳入內部元件——在建立更高層次的元件時非常有用,這裡在傳遞時的使用方法和 $attrs 十分類似。

    程式碼例項

    在這個例項中,共有三個元件:A、B、C,其關係為:[ A [ B [C] ] ],A為B的父元件,B為C的父元件。即:1級元件A,2級元件B,3級元件C。我們實現了:

    父向子傳值:1級元件A通過 :messageFromA="message" 將 message 屬性傳遞給2級元件B,2級元件B通過 $attrs.messageFromA 獲取到1級元件A的 message 。

    :messageFromA="message"
    v-bind="$attrs"
    $attrs.messageFromA

    子向父傳值:1級元件A通過 @keyup="receive" 在子孫元件上繫結keyup事件的監聽,2級元件B在通過 v-on="$listeners" 來將 keyup 事件繫結在其 input 標籤上。當2級元件B input 輸入框輸入時,便會觸發1級元件A的receive回撥,將2級元件B的 input 輸入框中的值賦值給1級元件A的 messageFromComp ,從而實現子向父傳值。

    @keyup="receive"
    <CompC v-on="$listeners" />
    v-on="$listeners"

    程式碼如下:

    // 3級元件C
    <template>
      <div class="compc">
        <h5>this is C component</h5>
        <input name="compC" type="text" v-model="message" v-on="$listeners" /> <!--將A元件keyup的監聽回撥綁在該input上-->
        <p>收到來自A元件的訊息:{{ $attrs.messageFromA }}</p>
      </div>
    </template>
    <script>
    export default {
      name: 'Compc',}
    </script>
    // 2級元件B
    <template>
      <div class="compb">
        <h4>this is B component</h4>
        <input name="compB" type="text" v-model="message" v-on="$listeners" />  <!--將A元件keyup的監聽回撥綁在該input上-->
        <p>收到來自A元件的訊息:{{ $attrs.messageFromA }}</p>
        <CompC v-bind="$attrs" v-on="$l客棧isteners" /> <!--將A元件keyup的監聽回撥繼續傳遞給C元件,將A元件傳遞的attrs繼續傳遞給C元件-->
      </div>
    </template>
    <script>
    import CompC from './compC'
    export default {
      name: 'CompB',components: {
        CompC,}
    </script>
    // A元件
    <template>
      <div class="compa">
        <h3>this is A component</h3>
        <input type="text" v-model="message" />
        <p>收到來自{{ comp }}的訊息:{{ messageFromComp }}</p>
        <CompB :messageFromA="message" @keyup="receive" />  <!--監聽子孫元件的keyup事件,將message傳遞給子孫元件-->
      </div>
    </template>
    <script>
    import CompB from './compB'
    export default {
      name: 'CompA',messageFromComp: '',comp: '',components: {
        CompB,methods: {
        receive(e) { // 監聽子孫元件keyup事件的回撥,並將keyup所在input輸入框的值賦值給messageFromComp
          this.comp = e.target.name
          this.messageFromComp = e.target.value
        },}
    </script>

    效果預覽

    深入瞭解Vue元件七種通訊方式

    5. provide/inject

    簡介

    provide/inject這對選項需要一起使用,以允許一個祖先元件向其所有子孫後代注入一個依賴,不論元件層次有多深,並在其上下游關係成立的時間裡始終生效。如果你是熟悉React的同學,你一定會立刻想到Context這個api,二者是十分相似的。

    provide:是一個物件,或者是一個返回物件的函式。該物件包含可注入其子孫的 property ,即要傳遞給子孫的屬性和屬性值。

    injcet:一個字串陣列,或者是一個物件。當其為字串陣列時,使用方式和props十分相似,只不過接收的屬性由data變成了provide中的屬性。當其為物件時,也和props類似,可以通過配置default和from等屬性來設定預設值,在子元件中使用新的命名屬性等。

    程式碼例項

    這個例項中有三個元件,1級元件A,2級元件B,3級元件C:[ A [ B [C] ] ],A是B的父元件,B是C的父元件。例項中實現了:

    父向子傳值:1級元件A通過provide將message注入給子孫元件,2級元件B通過 inject: ['messageFromA'] 來接收1級元件A中的message,並通過 messageFromA.content 獲取1級元件A中message的content屬性值。

    跨級向下傳值:1級元件A通過provide將message注入給子孫元件,3級元件C通過 inject: ['messageFromA'] 來接收1級元件A中的message,並通過 messageFromA.content 獲取1級元件A中message的content屬性值,實現跨級向下傳值。

    程式碼如下:

    // 1級元件A
    <template>
      <div class="compa">
        <h3>this is A component</h3>
        <input type="text" v-model="message.content" />
        <CompB />
      </div>
    </template>
    <script>
    import CompB from './compB'
    export default {
      name: 'CompA',provide() {
        return {
          messageFromA: this.message,// 將message通過provide傳遞給子孫元件
        }
      },data() {
        http://www.cppcns.comreturn {
          message: {
            content: '',}
    </script>
    // 2級元件B
    <template>
      <div class="compb">
        <h4>this is B component</h4>
        <p>收到來自A元件的訊息:{{ messageFromA && messageFromA.content }}</p>
        <CompC />
      </div>
    </template>
    <script>
    import CompC from './compC'
    export default {
      name: 'CompB',inject: ['messageFromA'],// 通過inject接受A中provide傳遞過來的message
      components: {
        CompC,}
    </script>
    // 3級元件C
    <template>
      <div class="compc">
        <h5>this is C component</h5>
        <p>收到來自A元件的訊息:{{ messageFromA && messageFromA.content }}</p>
      </div>
    </template>
    <script>
    export default {
      name: 'Compc',// 通過inject接受A中provide傳遞過來的message
    }
    </script>

    注意點:

    可能有同學想問我上面1級元件A中的message為什麼要用object型別而不是string型別,因為在vue provide 和 inject 繫結並不是可響應的。如果message是string型別,在1級元件A中通過input輸入框改變message值後無法再賦值給messageFromA,如果是object型別,當物件屬性值改變後,messageFromA裡面的屬性值還是可以隨之改變的,子孫元件inject接收到的物件屬性值也可以相應變化。

    子孫provide和祖先同樣的屬性,會在後代中覆蓋祖先的provide值。例如2級元件B中也通過provide向3級元件C中注入一個messageFromA的值,則3級元件C中的messageFromA會優先接收2級元件B注入的值而不是1級元件A。

    效果預覽

    深入瞭解Vue元件七種通訊方式

    6. eventBus

    簡介

    eventBus又稱事件匯流排,通過註冊一個新的Vue例項,通過呼叫這個例項的 $ emit 和 $ on等來監聽和觸發這個例項的事件,通過傳入引數從而實現元件的全域性通訊。它是一個不具備 DOM 的元件,有的僅僅只是它例項方法而已,因此非常的輕便。

    我們可以通過在全域性Vue例項上註冊:

    // main.
    Vue.prototype.$Bus = new Vue()

    但是當專案過大時,我們最好將事件匯流排抽象為單個檔案,將其匯入到需要使用的每個元件檔案中。這樣,它不會汙染全域性名稱空間:

    // bus.js,使用時通過import引入
    import Vue from 'vue'
    export const Bus = new Vue()

    原理分析

    eventBus的原理其實比較簡單,就是使用訂閱-釋出模式,實現 $ emit 和 $ on兩個方法即可:

    // eventBus原理
    export default class Bus {
      constructor() {
        this.callbacks = {}
      }
      $on(event,fn) {
        this.callbacks[event] = this.callbacks[event] || []
        this.callbacks[event].push(fn)
      }
      $emit(event,args) {
        this.callbacks[event].forEach((fn) => {
          fn(args)
        })
      }
    }
    
    // 在main.js中引入以下
    // Vue.prototype.$bus = new Bus()

    程式碼例項

    在這個例項中,共包含了4個元件:[ A [ B [ C、D ] ] ],1級元件A,2級元件B,3級元件C和3級元件D。我們通過使用eventBus實現了:

    全域性通訊:即包括了父子元件相互通訊、兄弟元件相互通訊、跨級元件相互通訊。4個元件的操作邏輯相同,都是在input輸入框時,通過 this.$bus.$emit('sendMessage',obj) 觸發sendMessage事件回撥,將sender和message封裝成物件作為引數傳入;同時通過 this.$bus.$on('sendMessage',obj) 監聽其他元件的sendMessage事件,例項當前元件示例sender和message的值。這樣任一元件input輸入框值改變時,其他元件都能接收到相應的資訊,實現全域性通訊。

    程式碼如下:

    // main.js
    Vue.prototype.$bus = new Vue()
    // 1級元件A
    <template>  
      <div class="containerA">   
        <h2>this is CompA</h2>  
        <input type="text" v-model="message" @keyup="sendMessage" />   
        <p v-show="messageFromBus && sender !== $options.name">      
          收到{{ sender }}的訊息:{{ messageFromBus }}   
        </p>   
        <CompB /> 
      </div>
    </template>
    <script>
    import CompB from './compB'
    export default {  
      name: 'CompA',components: {   
        CompB,data() {  
        return {    
          message: '',messageFromBus: '',sender: '',} 
      },mounted() {   
        this.$bus.$on('sendMessage',(obj) => {  // 通過eventBus監聽sendMessage事件     
          const { sender,message } = obj    
          this.sender = sender     
          this.messageFromBus = message  
        }) 
      },methods: {  
        sendMessage() {     
          this.$bus.$emit('sendMessage',{ // 通過eventBus觸發sendMessage事件     
            sender: this.$options.name,message: this.message,})   
        },}
    </script>
    // 2級元件B
    <template> 
      <div class="containerB">   
        <h3>this is CompB</h3>  
        <input type="text" v-model="message" @keyup="sendMessage" />  
        <p v-show="messageFromBus && sender !== $options.name">     
          收到{{ sender }}的訊息:{{ messageFromBus }}  
        </p>  
        <CompC />  
        <CompD /> 
      </div>
    </template>
    <script>
    import CompC from './compC'
    import CompD from './compD'
    export default { 
      name: 'CompB',components: {  
        CompC,CompD,mounted() {    
        this.$bus.$on('sendMessage',(obj) => { // 通過eventBus監聽sendMessage事件     
          const { sender,message } = obj     
          this.sender = sender     
          this.messageFromBus = message  
        }) 
      },methods: {  
        sendMessage() {   
          this.$bus.$emit('sendMessage',{ // 通過eventBus觸發sendMessage事件      
            sender: this.$options.name,})   
       },}
    </script>
    // 3級元件C
    <template> 
      <div class="containerC">  
        <p>this is CompC</p>   
        <input type="text" v-model="message" @keyup="sendMessage" />   
        <p v-show="messageFromBus && sender !== $options.name">      
          收到{{ sender }}的訊息:{{ messageFromBus }}  
        </p> 
      </div>
    </template>
    <script>
    export default {  
      name: 'CompC',data() {   
        return {   
          message: '',mounted() {  
        this.$bus.$on('sendMessage',(obj) => { // 通過eventBus監聽sendMessage事件    
          const { sender,message } = obj    
          this.sender = sender    
          this.messageFromBus = message   
        })  
      },methods: {   
        sendMessage() {    
          this.$bus.$emit('sendMessage',{ // 通過eventBus觸發sendMessage事件       
            sender: this.$options.name,})   
      www.cppcns.com  },}
    </script>
    // 3級元件D
    <template> 
      <div class="containerD">   
        <p>this is CompD</p>   
        <input type="text" v-model="message" @keyup="sendMessage" />   
        <p v-show="messageFromBus && sender !== $options.name">     
          收到{{ sender }}的訊息:{{ messageFromBus }} 
        </p> 
      </div>
    </template>
    <script>
    export default { 
      name: 'CompD',data() {  
        return {   
          message: '',message } = obj    
          this.sender = sender   
          this.messageFromBus = message   
        }) 
      },})  
        },}
    </script>

    效果預覽

    深入瞭解Vue元件七種通訊方式

    圖片過大,截圖處理

    7. Vuex

    當專案龐大以後,在多人維護同一個專案時,如果使用事件匯流排進行全域性通訊,容易讓全域性的變數的變化難以預測。於是有了Vuex的誕生。

    Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式。它採用集中式儲存管理應用的所有元件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。

    有關Vuex的內容,可以參考 Vuex官方文件 [1] ,我就不在這裡班門弄斧了,直接看程式碼。

    程式碼例項

    Vuex的例項和事件匯流排leisi,同樣是包含了4個元件:[ A [ B [ C、D ] ] ],1級元件A,2級元件B,3級元件C和3級元件D。我們在這個例項中實現了:

    全域性通訊:程式碼的內容和eventBus也類似,不過要比eventBus使用方便很多。每個元件通過watch監聽input輸入框的變化,把input的值通過vuex的commit觸發mutations,從而改變stroe的值。然後每個元件都通過computed動態獲取store中的資料,從而實現全域性通訊。

    // store.js
    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex)
    export default new Vuex.Store({
      state: {
        message: {
          sender: '',content: '',mutations: {
        sendMessage(state,obj) {
          state.message = {
            sender: obj.sender,content: obj.content,}
        },})
    // 元件A
    <template>
      <div class="containerA">
        <h2>this is CompA</h2>
        <input type="text" v-model="message" />
        <p v-show="messageFromStore && sender !== $options.name">
          收到{{ sender }}的訊息:{{ messageFromStore }}
        </p>
        <CompB />
      </div>
    </template>
    <script>
    import CompB from './compB'
    export default {
      name: 'CompA',computed: {
        messageFromStore() {
          return this.$store.state.message.content
        },sender() {
          return this.$store.state.message.sender
        },watch: {
        message(newValue) {
          this.$store.commit('sendMessage',{
            sender: this.$options.name,www.cppcns.com      content: newValue,})
        },}
    </script>

    同樣和eventBus中一樣,B,C,D元件中的程式碼除了引入子元件的不同,script部分都是一樣的,就不再往上寫了。

    效果預覽

    深入瞭解Vue元件七種通訊方式

    總結

    上面總共提到了7中Vue的元件通訊方式,他們能夠進行的通訊種類如下圖所示:

    深入瞭解Vue元件七種通訊方式

    • props/$emit:可以實現父子元件的雙向通訊,在日常的父子元件通訊中一般會作為我們的最常用選擇。
    • v-slot:可以實現父子元件單向通訊(父向子傳值),在實現可複用元件,向元件中傳入DOM節點、html等內容以及某些元件庫的表格值二次處理等情況時,可以優先考慮v-slot。
    • $ refs/$ parent/ $ children/ $ r oot: 可 以實現父子元件雙向通訊,其中 $root可以實現根元件例項向子孫元件跨級單向傳值。 在父元件沒有傳遞值或通過v-on繫結監聽時,父子間想要獲取彼此的屬性或方法可以考慮使用這些api。
    • $ attrs/ $ listeners: 能夠實現跨級雙向通訊,能夠讓你簡單的獲取傳入的屬性和繫結的監聽,並且方便地向下級子元件傳遞,在構建高階元件時十分好用。
    • provide/inject:可以實現跨級單向通訊,輕量地向子孫元件注入依賴,這是你在實現高階元件、建立元件庫時的不二之選。
    • eventBus:可以實現全域性通訊,在專案規模不大的情況下,可以利用eventBus實現全域性的事件監聽。但是eventBus要慎用,避免全域性汙染和記憶體洩漏等情況。
    • Vuex:可以實現全域性通訊,是vue專案全域性狀態管理的最佳實踐。在專案比較龐大,想要集中式管理全域性元件狀態時,那麼安裝Vuex準沒錯!

    以上就是深入瞭解Vue元件七種通訊方式的詳細內容,更多關於Vue元件通訊方式的資料請關注我們其它相關文章!