Vue組件通訊方式全面詳解

vue組件通訊方式全面詳解

眾所周知,Vue主要思想就是組件化開發。因為,在實際的項目開發中,肯定會以組件的開發模式進行。形如頁面和頁面之間需要通訊一樣,Vue 組件和組件之間肯定也需要互通有無、共享狀態。接下來,我們就悉數給大家展示所有 Vue 組件之間的通訊方式。

組件關係

  • App組件和A組件、A組件和B組件、B組件和C組件形成父子關係
  • B組件和D組件形成兄弟關係
  • App組件和C組件、App和B組件形成了隔代關係(其中的層級可能是多級,既隔多代)

組件通訊


這麼多的組件關係,那麼組件和組件之間又有哪些通訊的方式呢?各種方式的區別又是什麼?適用場景又是什麼呢?

props和$emit

這種方式是我們日常開發中應用最多的一種方式。

props以單向數據流的形式可以很好的完成父子組件的通訊

所謂單向數據流:就是數據只能通過 props 由父組件流向子組件,而子組件並不能通過修改 props 傳過來的數據修改父組件的相應狀態。至於為什麼這樣做,Vue 官網做出了解釋:

**所有的 prop 都使得其父子 prop 之間形成了一個單向下行綁定:父級 prop 的更新會向下流動到子組件中,但是反過來則不行。這樣會防止從子組件意外改變父級組件的狀態,從而導致你的應用的數據流向難以理解。

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

正因為這個特性,於是就有了對應的 $emit$emit 用來觸發當前實例上的事件。對此,我們可以在父組件自定義一個處理接受變化狀態的邏輯,然後在子組件中如若相關的狀態改變時,就觸發父組件的邏輯處理事件。

let Child = {     template: `<div>  <input type="text" v-model='msg'/>  <button @click='handleClick'>傳遞</button>  </div>`,     props: ['msg'],     methods: {         handleClick() {             this.$emit('getChildData', '子組件數據')         }     },    }  let Parent = {     data() {         return {             msg: '小馬哥',             val:''         }     },     methods: {         getChildData(val) {             this.val = val;         }     },     template: `  <div>  <p>我是一個父組件</p>  <p>我是{{val}}</p>  <Child :msg='msg' @getChildData='getChildData'></Child>  </div>  `,     components: {         Child     }  }    let vm = new Vue({     el: '#app',     template: `  <div>  <Parent></Parent>  </div>  `,     components: {         Parent     }    })  
  1. 父傳子:父組件傳遞msg數據給子組件,通過v-bind綁定msg,子組件中直接可以用props接收綁定的數據
  2. 子傳父:子組件觸發相應的事件,通過$emit觸發事件傳遞數據,父組件中綁定對應的事件,通過$on監聽對應的事件 接收子組件傳遞的數據

EventBus-中央事件匯流排

如果想實現兄弟組件之間進行通訊,在項目規模不大的情況下,完全可以使用中央事件匯流排EventBus的方式。如果你的項目規模是大中型的,那我們會使用vuex狀態管理

EventBus通過新建一個Vue事件bus對象,通過bus.$emit觸發事件,bus.$on監聽觸發的事件。

Vue.component('A', {      template: `  <div>  <p>我是A組件</p>  <button @click='handleClick'>A傳遞到B</button>  </div>  `,      data() {          return {              msg: 'hello 小馬哥'          }      },      methods: {          handleClick() {              this.$bus.$emit('globalEvent',this.msg);          }      },  })  Vue.component('B', {      template: `  <div>  <p>我是B組件</p>  <h3>{{aValue}}</h3>  </div>  `,      data() {          return {              aValue: ''          }      },      created () {          this.$bus.$on('globalEvent',(val)=>{              this.aValue = val;          })      },  })  // 定義中央事件匯流排  let bus = new Vue();  // 將中央事件匯流排賦值給Vue.prototype中,這樣所有組件都能訪問到了  Vue.prototype.$bus = bus;    let vm = new Vue({      el: '#app',      template: `  <div>  <A></A>  <B></B>  </div>  `,      })  

$attrs和$listeners

通過 props進行組件通訊的方式只適合直接的父子組件,如果父組件A下面有子組件B,組件B下面有組件C,這時如果組件A直接想傳遞數據給組件C那就行不通了! 只能是組件A通過 props 將數據傳給組件B,然後組件B獲取到組件A 傳遞過來的數據後再通過 props 將數據傳給組件C。當然這種方式是非常複雜的,無關組件中的邏輯業務一種增多了,程式碼維護也沒變得困難,再加上如果嵌套的層級越多邏輯也複雜,無關程式碼越多!

針對這樣一個問題,Vue 2.4提供了$attrs$listeners來實現能夠直接讓組件A傳遞消息給組件C

Vue.component('A', {        template: `        <div>          <p>我是A組件</p>          <B :msg='msg' @getCData='getCData'></B>        </div>       `,        methods: {          getCData(val) {            alert(val)          }        },        data() {          return {            msg: 'hello 小馬哥'          }        },      })      Vue.component('B', {        template: `        <div>          <p>我是B組件</p>           <!-- C組件中能直接觸發 getCData 的原因在於:B組件調用 C組件時,使用 v-on 綁定了 $listeners 屬性 -->           <!-- 通過v-bind 綁定 $attrs 屬性,C組件可以直接獲取到 A組件中傳遞下來的 props(除了 B組件中 props聲明的) -->          <C v-bind='$attrs' v-on='$listeners'></C>        </div>       `,        // props: ['msg'],        data() {          return {            }        }      })      Vue.component('C', {        template: `      <div>        <p>我是C組件</p>        <p>{{$attrs.msg}}</p>        <button @click='handleClick'>傳遞數據</button>      </div>      `,        methods: {          handleClick() {            this.$emit('getCData', 'C組件的數據')          }        },        data() {          return {            }        }      })      let vm = new Vue({        el: '#app',        template: `        <div>          <A></A>        </div>        `,          })  
  • $attrs:包含了父作用域中不被 prop 所識別 (且獲取) 的特性綁定 (classstyle 除外)。當一個組件沒有聲明任何 prop 時,這裡會包含所有父作用域的綁定屬性 (class和 style 除外),並且可以通過 v-bind="$attrs" 傳入內部組件。
  • $listeners:包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它可以通過 v-on="$listeners" 傳入內部組件。

provide和inject

在父組件中通過 provider 來提供屬性,然後在子組件中通過 inject 來注入變數。不論子組件有多深,只要調用了 inject 那麼就可以注入在 provider 中提供的數據,而不是局限於只能從當前父組件的 prop 屬性來獲取數據,只要在父組件的生命周期內,子組件都可以調用。這和 React 中的 Context API 有沒有很相似!

Vue.component('A', {        template: `        <div>          <p>我是A組件</p>          <B></B>        </div>       `,        provide:{          a:"祖先A的數據"        },        data() {          return {            msg: 'hello 小馬哥'          }        },      })      Vue.component('B', {        template: `        <div>          <p>我是B組件</p>          <C></C>        </div>       `,          data() {          return {            }        }      })      Vue.component('C', {        template: `      <div>        <p>我是C組件</p>        <h3>{{a}}</h3>      </div>      `,      inject:['a'],        data() {          return {            }        }      })      let vm = new Vue({        el: '#app',        template: `        <div>          <A></A>        </div>        `,          })  
  • parent 組件中,通過 provide 屬性,以對象的形式向子孫組件暴露了一些屬性
  • child 組件中,通過 inject 屬性注入了 parent 組件提供的數據,實際這些通過 inject 注入的屬性是掛載到 Vue 實例上的,所以在組件內部可以通過 this 來訪問

⚠️ 注意:官網文檔提及 provide 和 inject 主要為高階插件/組件庫提供用例,並不推薦直接用於應用程式程式碼中。

$parent和$children

這裡要說的這種方式就比較直觀了,直接操作父子組件的實例。$parent 就是父組件的實例對象,而 $children 就是當前實例的直接子組件實例了,不過這個屬性值是數組類型的,且並不保證順序,也不是響應式的。

Vue.component('Parent', {       template: `       <div>         <p>我是父組件</p>         {{msg}}         <hr/>         <Child></Child>       </div>      `,       mounted () {         //讀取子組件數據,注意$children並不保證順序,也不是響應式的         console.log(this.$children[0].a)       },       data() {         return {           msg: 'hello 小馬哥'         }       },     })     Vue.component('Child', {       template: `       <div>         <p>我是孩子組件</p>         <input type="text" @input='changeValue'/>         <h2>{{myMsg}}</h2>       </div>      `,       methods: {         changeValue() {           this.$parent.msg = '子組件中的數據'         }       },       data() {         return {           myMsg:this.$parent.msg,           a:"小馬哥"         }       }     })       let vm = new Vue({       el: '#app',       template: `       <div>         <Parent></Parent>       </div>       `,     })  

Vuex狀態管理

Vuex 是狀態管理工具,實現了項目狀態的集中式管理。工具的實現借鑒了 FluxRedux、和 The Elm Architecture 的模式和概念。當然與其他模式不同的是,Vuex 是專門為 Vue.js 設計的狀態管理庫,以利用 Vue.js 的細粒度數據響應機制來進行高效的狀態更新。詳細的關於 Vuex 的介紹,你既可以去查看官網文檔,也可以查看本專欄關於 Vuex 一系列的介紹。