最近在用element-ui重構前端項目,無意之中翻閱到一個比較好用的組件間通信方式,借助於vue的封裝的發布-訂閱消息模式與mixin語法。在開始之前先總結下vue常用的組件間通信方式,具體如下:
1、props與自定義事件
優點:常用的父子、子父組件傳遞方式,簡單易懂
缺點:子父、父子之間傳參比較高效,但是爺孫,兄弟組件之間存在通信短板,只能一級級傳遞
2、vue 2.4中新增的$attrs與$listeners
優點:解決了組件嵌套層次較深問題,通過在組件中綁定組件的屬性值與監聽組件的事件監聽對象,子組件可以輕松訪問到上層作用域的事件回調方法
缺點:相對於props與自定義事件,只是簡化了寫法,本質上屬性也是一級級傳遞的,如果組件的層級是A>B>C,C組件如果想要訪問到A組件的事件回調,那么在B組件中需要綁定A組件的屬性attrs與事件對象listeners。
3、借助於發布-訂閱模式
優點:發布-訂閱設計模式所擅長點就是解決模塊間通信,無論何種方式的通信方式,理論上都可以解決,在vue中,大量使用了該設計模式,只需要實例化一個vue實例,作為一個單獨的事件中心引入即可。發布訂閱模式具體內容可參考:https://www.cnblogs.com/gerry2019/p/10241488.html
缺點:發布-訂閱模式只要有訂閱消息,在分發事件的時候,都會觸發訂閱的回調,所以在開發過程中要注意自定義事件的命名空間,防止一些不必要的觸發操作。
4、借助vuex管理狀態
優點:引入狀態管理,可以統一的管理項目狀態,針對大型復雜項目適用性較好
缺點:需要額外維護一個vuex對象,中小型項目不適合
5、vue 2.2中新增的provide/inject
優點:可以從父組件注入需要共享給子元素的數據,如果注入的的數據是響應式,那么組件之間可以實現雙向數據通信
缺點:官方並不推薦在業務代碼中使用此模式,因為這種雙向通信可能會導致組件的狀態發生混亂
以上為vue中常用的幾種組件間通信方式,以下為element-ui中采用的組件通信方式,本質上也是發布-訂閱模式,與上述方法3大致相似,只是引入了自定義事件的生效的作用域,可以規避一些意料之外的事件觸發。因為稍微不注意消息的命名空間,就可能會導致一些意外之外的錯誤,除此之外,當兩個模塊功能相似,如果單獨都寫一遍,可能存在大量冗余代碼,在vue中我們想到的策略可能是借助於mixin,抽離公共的業務代碼,但是假如抽離出了公共的消息訂閱內容,那么此時問題就來了,如果我只想A模塊內容發生改變,B模塊不變,這個就會存在策略性問題,element-ui中的通信策略可以很好的規避掉以上兩種坑,具體實現邏輯如下:
/** * * @param {目標組件名稱} componentName * @param {事件名稱} eventName * @param {載荷參數} params */ function broadcast(componentName, eventName, params) { //遞歸子組件,查找命名空間內組件 this.$children.forEach(child => { var name = child.$options.componentName; if (name === componentName) { //分發子組件內訂閱消息 child.$emit.apply(child, [eventName].concat(params)); } else { broadcast.apply(child, [componentName, eventName].concat([params])); } }); } export default { methods: { /** * * @param {目標組件名稱} componentName * @param {事件名稱} eventName * @param {載荷參數} params */ dispatch(componentName, eventName, params) { var parent = this.$parent || this.$root; var name = parent.$options.componentName; //循環查詢父組件,找到目標父組件 while (parent && (!name || name !== componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.componentName; } } if (parent) { //分發父組件訂閱內容 parent.$emit.apply(parent, [eventName].concat(params)); } }, broadcast(componentName, eventName, params) { broadcast.call(this, componentName, eventName, params); } } };
從以上代碼可以看出,在組件通信過程中,需要額外加一個componetName屬性,其主要作用就是對分發的事件加入一個命名空間,命名空間內部的事件才會觸發,否則會直接跳過,增加了業務代碼的容錯能力。在使用的過程中,只需要在需要引用的地方引入這個混入,就可以實現組件間雙向通信。element-ui中的具體使用如下:
mixin語法可參考:https://cn.vuejs.org/v2/guide/mixins.html
mixin本質上就類似於繼承,有助於簡化業務代碼
vue 2.x中部分新增組件通信方式可參閱vue API文檔:https://cn.vuejs.org/v2/api/#provide-inject