Vue元件間的通訊方式詳析

2022-10-01 10:00:08
本篇文章給大家帶來了關於的相關知識,主要介紹了Vue元件間通訊方式,Vue元件間通訊一直是個重要的話題,雖然官方推出的Vuex狀態管理方案可以很好的解決元件之間的通訊問題,但是在元件庫內部使用Vuex往往會比較重,下面一起來看一下,希望對大家有幫助。

前端(vue)入門到精通課程:進入學習
API 檔案、設計、偵錯、自動化測試一體化共同作業工具:

【相關推薦:、】

在Vue元件庫開發過程中,Vue元件之間的通訊一直是一個重要的話題,雖然官方推出的 Vuex 狀態管理方案可以很好的解決元件之間的通訊問題,但是在元件庫內部使用 Vuex 往往會比較重,本文將系統的羅列出幾種不使用 Vuex,比較實用的元件間的通訊方式,供大家參考。

元件之間通訊的場景

在進入我們今天的主題之前,我們先來總結下Vue元件之間通訊的幾種場景,一般可以分為如下幾種場景:

  • 父子元件之間的通訊
  • 兄弟元件之間的通訊
  • 隔代元件之間的通訊

父子元件之間的通訊

父子元件之間的通訊應該是 Vue 元件通訊中最簡單也最常見的一種了,概括為兩個部分:父元件通過prop向子元件傳遞資料,子元件通過自定義事件向父元件傳遞資料。

父元件通過 prop 向子元件傳遞資料

Vue元件的資料流向都遵循單向資料流的原則,所有的 prop 都使得其父子 prop 之間形成了一個單向下行繫結:父級 prop 的更新會向下流動到子元件中,但是反過來則不行。這樣會防止從子元件意外變更父級元件的狀態,從而導致你的應用的資料流向難以理解。

額外的,每次父級元件發生變更時,子元件中所有的 prop 都將會重新整理為最新的值。這意味著你不應該在一個子元件內部改變 prop。如果你這樣做了,Vue 會在瀏覽器的控制檯中發出警告。

父元件 ComponentA:


登入後複製

子元件 ComponentB:


登入後複製

子元件通過自定義事件向父元件傳遞資料

在子元件中可以通過 $emit 向父元件發生一個事件,在父元件中通過 v-on/@ 進行監聽。

子元件 ComponentA:


登入後複製

子元件 ComponentB:


登入後複製

這個例子非常簡單,在子元件 ComponentB 裡面通過 $emit 派發一個事件 title-change,在父元件 ComponentA 通過 @title-change 繫結的 titleChange 事件進行監聽,ComponentB 向 ComponentA 傳遞的資料在 titleChange 函數的傳參中可以獲取到。

兄弟元件之間的通訊

狀態提升

寫過 React 的同學應該對元件的 狀態提升 概念並不陌生,React 裡面將元件按照職責的不同劃分為兩類:展示型元件(Presentational Component)容器型元件(Container Component)

展示型元件不關心元件使用的資料是如何獲取的,以及元件資料應該如何修改,它只需要知道有了這些資料後,元件UI是什麼樣子的即可。外部元件通過 props 傳遞給展示型元件所需的資料和修改這些資料的回撥函數,展示型元件只是它們的使用者。

容器型元件的職責是獲取資料以及這些資料的處理邏輯,並把資料和邏輯通過 props 提供給子元件使用。

因此,參考 React 元件中的 狀態提升 的概念,我們在兩個兄弟元件之上提供一個父元件,相當於容器元件,負責處理資料,兄弟元件通過 props 接收引數以及回撥函數,相當於展示元件,來解決兄弟元件之間的通訊問題。

ComponentA(兄弟元件A):


登入後複製

ComponentB(兄弟元件B):


登入後複製

ComponentC(容器元件C):


登入後複製

可以看到,上述這種 "狀態提升" 的方式是比較繁瑣的,特別是兄弟元件的通訊還要藉助於父元件,元件複雜之後處理起來是相當麻煩的。

隔代元件之間的通訊

隔代元件之間的通訊可以通過如下幾種方式實現:

  • $attrs/$listeners
  • rovide/inject
  • 基於 $parent/$children 實現的 dispatchbroadcast

attrs/attrs/listeners

Vue 2.4.0 版本新增了 $attrs$listeners 兩個方法。先看下官方對 $attrs 的介紹:

包含了父作用域中不作為 prop 被識別 (且獲取) 的 attribute 繫結(classstyle 除外)。當一個元件沒有宣告任何 prop 時,這裡會包含所有父作用域的繫結 (classstyle 除外),並且可以通過 v-bind="$attrs" 傳入內部元件——在建立高階別的元件時非常有用。

看個例子:

元件A(ComponentA):


登入後複製

元件B(ComponetB):


登入後複製

元件C(ComponetC):


登入後複製

這裡有三個元件,祖先元件(ComponentA)、父元件(ComponentB)和子元件(ComponentC)。這三個元件構成了一個典型的子孫元件之間的關係。

ComponetA 給 ComponetB 傳遞了三個屬性 name、age 和 sex,ComponentB 通過 v-bind="$attrs" 將這三個屬性再透傳給 ComponentC, 最後在 ComponentC 中列印 $attrs 的值為:

{age: '24', sex: 'male'}
登入後複製

為什麼我們一開始傳遞了三個屬性,最後只列印了兩個屬性 age 和 sex 呢?因為在 ComponentC 的props 中宣告了 name 屬性,$attrs 會自動排除掉在 props 中宣告的屬性,並將其他屬性以物件的形式輸出。

說白了就是一句話,$attrs 可以獲取父元件中繫結的非 Props 屬性

一般在使用的時候會同時和 inheritAttrs 屬性配合使用。

如果你不希望元件的根元素繼承 attribute,你可以在元件的選項中設定 inheritAttrs: false

在 ComponentB 新增了 inheritAttrs=false 屬性後,ComponentB 的dom結構中可以看到是不會繼承父元件傳遞過來的屬性:

image.png

如果不加上 inheritAttrs=false 屬性,就會自動繼承父元件傳遞過來的屬性:

image.png

再看下 $listeners 的定義:

包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它可以通過 v-on="$listeners" 傳入內部元件——在建立更高層次的元件時非常有用。

$listeners也能把父元件中對子元件的事件監聽全部拿到,這樣我們就能用一個v-on把這些來自於父元件的事件監聽傳遞到下一級元件。

繼續改造 ComponentB 元件:


登入後複製

這裡利用 $attrs$listeners 方法,可以將祖先元件(ComponentA) 中的屬性和事件透傳給孫元件(ComponentC),這樣就可以實現隔代元件之間的通訊。

provide/inject

provide/inject 是 Vue 2.2.0 版本後新增的方法。

這對選項需要一起使用,以允許一個祖先元件向其所有子孫後代注入一個依賴,不論元件層次有多深,並在其上下游關係成立的時間裡始終生效。如果你熟悉 React,這與 React 的上下文特性很相似。

先看下簡單的用法:

父級元件:

export default {
  provide: {
    name: 'Lin'
  }
}
登入後複製

子元件:

export default {
  inject: ['name'],
  mounted () {
    console.log(this.name);  // Lin
  }
}
登入後複製

上面的例子可以看到,父元件通過 privide 返回的物件裡面的值,在子元件中通過 inject 注入之後可以直接存取到。

但是需要注意的是,provideinject 繫結並不是可響應的,按照官方的說法,這是刻意為之的

也就是說父元件 provide 裡面的name屬性值變化了,子元件中 this.name 獲取到的值不變。

如果想讓 provide 和 inject 變成可響應的,有以下兩種方式:

  • provide 祖先元件的範例,然後在子孫元件中注入依賴,這樣就可以在子孫元件中直接修改祖先元件的範例的屬性,不過這種方法有個缺點就是這個範例上掛載很多沒有必要的東西比如props,methods
  • 使用 Vue 2.6 提供的 Vue.observable 方法優化響應式 provide

看一下第一種場景:

祖先元件元件(ComponentA):

export default {
  name: 'ComponentA',
  provide() {
    return {
      app: this
    }
  },
  data() {
    return {
       appInfo: {
         title: ''
       }
    }
  },
  methods: {
    fetchAppInfo() {
      this.appInfo = { title: 'Welcome to Vue world'}
    }
  }
}
登入後複製

我們把整個 ComponentA.vue 的範例 this 對外提供,命名為 app。接下來,任何元件只要通過 inject 注入 app 的話,都可以直接通過 this.app.xxx 來存取 ComponentA.vue 的 datacomputedmethods 等內容。

子元件(ComponentB):


登入後複製

這樣,任何子元件,只要通過 inject 注入 app 後,就可以直接存取祖先元件中的資料了,同時也可以呼叫祖先元件提供的方法修改祖先元件的資料並反應到子元件上。

當點選子元件(ComponentB)的獲取App資訊按鈕,會呼叫 this.app.fetchAppInfo 方法,也就是存取祖先元件(ComponentA)範例上的 fetchAppInfo 方法,fetchAppInfo 會修改fetchAppInfo的值。同時子元件(ComponentB)中會監聽 this.app.appInfo 的變化,並將變化後的title值顯示在元件上。

再看一下第二種場景,通過 Vue.observable 方法來實現 provideinject 繫結並可響應。

基於上面的範例,改造祖先元件(ComponentA):

import Vue from 'vue'

const state = Vue.observable({ title: '' });
export default {
  name: 'ComponentA',
  provide() {
    return {
      state
    }
  }
}
登入後複製

使用 Vue.observable 定義一個可響應的物件 state,並在 provide 中返回這個物件。

改造子元件(ComponentB):


登入後複製

與之前的例子不同的是,這裡我們直接修改了 this.state.title 的值,因為 state 被定義成了一個可響應的資料,所以 state.title 的值被修改後,檢視上的 title 也會立即響應並更新,從這裡看,其實很像 Vuex 的處理方式。

以上兩種方式對比可以發現,第二種藉助於 Vue.observable 方法實現 provideinject 的可響應更加簡單高效,推薦大家使用這種方式。

基於 $parent/$children 實現的 dispatch 和 broadcast

先了解下 dispatch 和 broadcast 兩個概念:

  • dispatch: 派發,指的是從一個元件內部向上傳遞一個事件,並在元件內部通過 $on 進行監聽
  • broadcast: 廣播,指的是從一個元件內部向下傳遞一個事件,並在元件內部通過 $on 進行監聽

在實現 dispatch 和 broadcast 方法之前,先來看一下具體的使用方法。有 ComponentA.vueComponentB.vue 兩個元件,其中 ComponentB 是 ComponentA 的子元件,中間可能跨多級,在 ComponentA 中向 ComponentB 通訊:

元件ComponentA:


登入後複製

元件ComponentB:

export default {
  name: 'ComponentB',
  created () {
    this.$on('on-message', this.showMessage)
  },
  methods: {
    showMessage (text) {
      console.log(text)
    }
  }
}
登入後複製

dispatch 的邏輯寫在 emitter.js 中,使用的時候通過 mixins 混入到元件中,這樣可以很好的將事件通訊邏輯和元件進行解耦。

dispatch 的方法有三個傳參,分別是:需要接受事件的元件的名字(全域性唯一,用來精確查詢元件)、事件名和事件傳遞的引數。

dispatch 的實現思路非常簡單,通過 $parent 獲取當前父元件物件,如果元件的name和接受事件的name一致(dispatch方法的第一個引數),在父元件上呼叫 $emit 發射一個事件,這樣就會觸發目標元件上 $on 定義的回撥函數,如果當前元件的name和接受事件的name不一致,就遞迴地向上呼叫此邏輯。

dispath:

export default {
  methods: {
    dispatch(componentName, eventName, params) {
      let parent = this.$parent || this.$root;
      let name = parent.$options.name;
      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;
        if (parent) {
          name = parent.$options.name
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    }
  }
}
登入後複製

broadcast邏輯和dispatch的邏輯差不多,只是一個是通過 $parent 向上查詢,一個是通過 $children 向下查詢,

export default {
  methods: {
    broadcast(componentName, eventName, params) {
      this.$children.forEach(child => {
        const name = child.$options.name
        if (name === componentName) {
          child.$emit.apply(child, [eventName].concat(params))
        } else {
          broadcast.apply(child, [componentName, eventName].concat([params]))
        }
      })
    }
  }
}
登入後複製

【相關推薦:、】

以上就是Vue元件間的通訊方式詳析的詳細內容,更多請關注TW511.COM其它相關文章!