前言
最近在學習餓了麼的Vue前端框架Element,發現其原始碼中大量使用了$broadcast
和$dispatch
方法,而Element使用的是Vue2.0版本,眾所周知在Vue 1.0升級到2.0中去除了$broadcast
和$dispatch
方法。
所以在Element框架原始碼中將這兩個函式重寫實現了一遍,並採用的是minix
的方式植入每個元件的程式碼中。
但是Element的這兩個函式雖然與官方同名,但功能卻有所差異,遂有本文,簡單分析一下區別於用途。
Element的broadcast功能分析
功能簡述
- 在Element中broadcast(事件廣播)方法需要3個引數,
componentName
元件名稱,eventName
事件名,和params
資料。 - broadcast是尋找所有子孫元件中,元件名為
componentName
的元件,若找到在其元件上觸發($emit)eventName
的事件方法,資料為params
。 - 假設若有3個子元件或孫子元件的元件名為指定的
componentName
的話,這三個元件上都會觸發其指定的事件方法。
原始碼對比
vue 1.0的官方$broadcast
的實現原始碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/** * Recursively broadcast an event to all children instances. * * @param {String|Object} event * @param {...*} additional arguments */ Vue.prototype.$broadcast = function (event) { var isSource = typeof event === 'string'; event = isSource ? event : event.name; // if no child has registered for this event, // then there's no need to broadcast. if (!this._eventsCount[event]) return; var children = this.$children; var args = toArray(arguments); if (isSource) { // use object event to indicate non-source emit // on children args[0] = { name: event, source: this }; } //遍歷所有一級子元件 for (var i = 0, l = children.length; i < l; i++) { var child = children[i]; //在每個元件上均觸發指定的事件 var shouldPropagate = child.$emit.apply(child, args); //若事件響應函式返回true才會向孫子元件繼續廣播 if (shouldPropagate) { child.$broadcast.apply(child, args); } } return this; }; |
Element的broadcast
的實現原始碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
broadcast(componentName, eventName, params) { broadcast.call(this, componentName, eventName, 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])); } }); } } |
官方的$broadcast
用途的解釋為:
廣播事件,通知給當前例項的全部後代。因為後代有多個枝杈,事件將沿著各“路徑”通知。每條路徑上的通知在觸發一個監聽器後停止,除非它返回 true。
Element的broadcast與Vue1.0官方的區別對比:
- 官方的
$broadcast
的引數只有兩個,event事件名和args事件資料。Element為三個,多一個元件名。 - 官方的
$broadcast
觸發方式是預設只觸發子代元件,不觸發孫子代元件,如果子代建立了監聽且返回了true,才會向孫子代元件傳遞事件。而Element是直接向所有子孫後代元件傳遞,也沒有返回值判定。 - 最重要的區別在於用途。Element的broadcast雖然與官方的同名,但是通過分析原始碼可以看出Element的用途應該是 遠端呼叫 或應取名叫
childEmit
,用途是呼叫/觸發指定子孫元件的事件。而非廣義上的“廣播”的概念。
最後,在Element的原始碼中如果找到了指定名稱的元件,並在其身上觸發了事件後,不會繼續在其身上查詢他的子元件,從用途上來講應該是找到所有符合名稱的子孫元件並觸發,所以為何會這樣原因不明。也許在Element的元件系統設計裡面,沒有自身套自身的情況?或是並不想在繼續觸發下級?需要再仔細分析才可了。
Element的dispatch功能分析
功能簡述
通過前面分析的$broadcast
可以大致推匯出Element中的dispatch
的主要功能。
依然是以尋找所有父級,直到找到要找的父元件,並在其身上觸發指定事件。
整體功能更類似jQuery的closest方法。
原始碼對比
vue 1.0的官方$dispatch
的實現原始碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/** * Recursively propagate an event up the parent chain. * * @param {String} event * @param {...*} additional arguments */ Vue.prototype.$dispatch = function (event) { var shouldPropagate = this.$emit.apply(this, arguments); if (!shouldPropagate) return; var parent = this.$parent; var args = toArray(arguments); // use object event to indicate non-source emit // on parents args[0] = { name: event, source: this }; while (parent) { shouldPropagate = parent.$emit.apply(parent, args); parent = shouldPropagate ? parent.$parent : null; } return this; }; |
Element的dispatch
的實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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; } } //找到符合元件名稱的父級後,觸發其事件。整體流程類似jQuery的closest方法 if (parent) { parent.$emit.apply(parent, [eventName].concat(params)); } }, |
最後
通過學習Element原始碼中的broadcast
和dispatch
的實現功能,從能力角度而言,Element是將官方的 事件廣播/事件派發 的功能弱化了,屬於是“閹割版”的。但是從實際開發過程中的易用性角度而言,Element的做法更貼合我們日常開發過程中的需求,如父元件呼叫元件的方法(如全選核取方塊等),子元件向父元件通知變更(如表單校驗等)。
Element是很優秀的Vue框架,值得深入學習來了解更深的Vue使用以及元件化思路。