您好,登錄后才能下訂單哦!
00 前言
$dispatch 和 $broadcast 作為一對情侶 💑屬性,在 Vue 1.0 中主要用來實現(xiàn)基于組件樹結(jié)構(gòu)的事件流通信 —— 通過向上或向下以冒泡的形式傳遞事件流,以實現(xiàn)嵌套父子組件的通信。但是由于其顯功能缺陷,在 Vue 2.0 中就被移除了。雖然 Vue 官網(wǎng)已經(jīng)不再支持使用 $dispatch 和 $broadcast 進(jìn)行組件通信,但是在很多基于 Vue 的 UI 框架中都有對其的封裝,包括 element-ui、iview 等等。
那么 $dispatch 和 $broadcast 到底是怎么工作,其底層又是怎么實現(xiàn)的呢?接下來,我們就詳細(xì)的說一說!
01 $dispatch 詳解
為了追根溯源,我們還是先去 Vue 1.0 的文檔你觀摩一下其概念吧!
概念:
Dispatch an event, first triggering it on the instance itself, and then propagates upward along the parent chain. The propagation stops when it triggers a parent event listener, unless that listener returns true. Any additional arguments will be passed into the listener's callback function.
上面的一段英文定義來自 Vue 1.0 官方文檔,其大致的意思是說:dispatch 是一個事件,首先會在自己實例本身上觸發(fā),然后沿父鏈向上傳播。當(dāng)它觸發(fā)父組件上的事件偵聽器時傳播即會停止,除非該偵聽器返回 true。 任何其他參數(shù)都將傳遞給偵聽器的回調(diào)函數(shù)。
參數(shù):
dispatch 會接收兩中參數(shù):event 是事件名稱,[...args] 是觸發(fā)事件時傳遞給回調(diào)函數(shù)的參數(shù)。
**例子:
// 創(chuàng)建一個 parent 組件 var parent = new Vue(); // 創(chuàng)建一個 child1 組件,其父組件指向 parent var child1 = new Vue({ parent: parent }); // 創(chuàng)建一個 child2 組件,其父組件指向 child1 var child2 = new Vue({ parent: child1 }); // 在 parent 組件監(jiān)聽名為 test 的事件,并綁定了一個回調(diào)函數(shù) parent.$on('test', function () { console.log('parent notified'); }); // 在 child1 組件監(jiān)聽名為 test 的事件,并綁定了一個回調(diào)函數(shù) child1.$on('test', function () { console.log('child1 notified'); }); // 在 child2 組件監(jiān)聽名為 test 的事件,并綁定了一個回調(diào)函數(shù) child2.$on('test', function () { console.log('child2 notified'); });
說到這里,parent、child1 和 child2 三個組件之間的關(guān)系可以展示成如下的關(guān)系圖:
// 在 child2 組件中通過 dispatch 觸發(fā) test 事件 child2.$dispatch('test'); // 事件執(zhí)行會輸出如下結(jié)果 // -> "child2 notified" // -> "child1 notified"
當(dāng)執(zhí)行 child2.$dispatch('test');
時,首先會觸發(fā) child2 組件里面監(jiān)聽的 test 事件的回調(diào)函數(shù),輸出 'child2 notified',根據(jù)上面官方文檔的定義,事件會沿著組件關(guān)系鏈一直向上傳遞,然后傳遞到 child1 組件,觸發(fā)監(jiān)聽事件輸出 "child1 notified",但是該偵聽器沒有返回 true,所以事件傳遞到此就結(jié)束了,最終的輸出結(jié)果就只有 "child2 notified" 和 "child1 notified"。
Vue 1.0 官方實現(xiàn)
在 Vue 1.0 版本中,$dispatch 實現(xiàn)的源碼放在 /src/instance/api/events.js 文件中,代碼很簡單:
/** * Recursively propagate an event up the parent chain. * 遞歸地在父鏈上傳播事件。 * @param {String} event * @param {...*} additional arguments */ // $dispatch 方法是定義在 Vue 的 prototype 上的 // 接受一個字符串類型的事件名稱 Vue.prototype.$dispatch = function (event) { // 首先執(zhí)行 $emit 觸發(fā)事件,將返回值保存在 shouldPropagate 中 var shouldPropagate = this.$emit.apply(this, arguments) // 如果首次執(zhí)行的 $emit 方法返回的值不是 true 就直接返回 // 如果返回值不是 true 就說明組件邏輯不希望事件繼續(xù)往父組件進(jìn)行傳遞 if (!shouldPropagate) return // 如果首次執(zhí)行 $emit 方法返回值是 true 就獲取當(dāng)前組件的 parent 組件實例 var parent = this.$parent // 將函數(shù)接受的參數(shù)轉(zhuǎn)換成數(shù)組 var args = toArray(arguments) // use object event to indicate non-source emit on parents // 根據(jù)傳入的事件名稱的參數(shù)組裝成 object args[0] = { name: event, source: this } // 循環(huán)知道組件的父組件 while (parent) { // 在父組件中執(zhí)行 $emit 觸發(fā)事件 shouldPropagate = parent.$emit.apply(parent, args) // 如果父組件 $emit 返回的是 true 就繼續(xù)遞歸祖父組件,否則就停止循環(huán) parent = shouldPropagate ? parent.$parent : null } // 最后返回當(dāng)前組件實例 return this }
element-ui 實現(xiàn)
在 element-ui 中,$dispatch 實現(xiàn)的源碼放在 /src/mixins/emitter.js 文件中,代碼很簡單:
// 定義 dispatch 方法,接受三個參數(shù),分別是:組件名稱、將要觸發(fā)的事件名稱、回調(diào)函數(shù)傳遞的參數(shù) dispatch(componentName, eventName, params) { // 獲取基于當(dāng)前組件的父組件實例,這里對父組件實例和根組件實例做了兼容處理 var parent = this.$parent || this.$root; // 通過父組件的 $option 屬性獲取組件的名稱 var name = parent.$options.componentName; // 當(dāng)相對當(dāng)前組件的父組件實例存在,而且當(dāng)父組件的名稱不存在或者父組件的名稱不等于傳入的組件名稱時,執(zhí)行循環(huán) while (parent && (!name || name !== componentName)) { // 記錄父組件的父組件 parent = parent.$parent; // 當(dāng)父組件的父組件存在時,獲取祖父組件的名稱 if (parent) { name = parent.$options.componentName; } } // 當(dāng)循環(huán)結(jié)束是,parent 的值就是最終匹配的組件實例 if (parent) { // 當(dāng) parent 值存在時調(diào)用 $emit 方法 // 傳入 parent 實例、事件名稱與 params 參數(shù)組成的數(shù)組 // 觸發(fā)傳入事件名稱 eventName 同名的事件 parent.$emit.apply(parent, [eventName].concat(params)); } }
差異分析
仔細(xì)看完實現(xiàn) $dispatch 方式的兩個版本的代碼,大家是不是發(fā)現(xiàn),兩個版本的實現(xiàn)和功能差異性還是很大的。
1、接受參數(shù):Vue 實現(xiàn)版本只會接受一個字符串類型的事件名稱為參數(shù),而 element-ui 實現(xiàn)的版本會接受三個參數(shù),分別是:需要觸發(fā)事件的組件名稱、將要觸發(fā)的事件名稱、回調(diào)函數(shù)傳遞的參數(shù);
2、實現(xiàn)功能:Vue 實現(xiàn)版本觸發(fā)事件一直會順著組件鏈向上進(jìn)行傳遞,知道父組件中的偵聽器沒有返回 true,在這個期間所有的組件都會執(zhí)行事件的響應(yīng),包括當(dāng)前組件本身,而 element-ui 實現(xiàn)版本會不斷的基于當(dāng)前組件向父組件進(jìn)行遍歷,直至找到和接受的組件名稱匹配,就會停止遍歷,觸發(fā)匹配組件中的監(jiān)聽事件。
10 $broadcast 詳解
上面詳細(xì)的說完 $dispatch 方法的實現(xiàn)和 Vue 實現(xiàn)版本與 element-ui 實現(xiàn)版本的區(qū)別,下面就該說說 $broadcast,畢竟他們是情侶屬性嘛。
概念
Broadcast an event that propagates downward to all descendants of the current instance. Since the descendants expand into multiple sub-trees, the event propagation will follow many different “paths”. The propagation for each path will stop when a listener callback is fired along that path, unless the callback returns true.
broadcast 是一個事件,它向下傳播到當(dāng)前實例的所有后代。由于后代擴(kuò)展為多個子樹,事件傳播將會遵循許多不同的“路徑”。 除非回調(diào)返回 true,否則在沿該路徑觸發(fā)偵聽器回調(diào)時,每個路徑的傳播將會停止。
參數(shù)
broadcast 會接收兩中參數(shù):event 是事件名稱,[...args] 是觸發(fā)事件時傳遞給回調(diào)函數(shù)的參數(shù)。
例子
// 創(chuàng)建 parent 組件實例 var parent = new Vue() // 創(chuàng)建 child1 組件實例,其父組件指向 parent var child1 = new Vue({ parent: parent }) // 創(chuàng)建 child2 組件實例,其父組件指向 parent var child2 = new Vue({ parent: parent }) // 創(chuàng)建 child3 組件實例,其父組件指向 child2 var child3 = new Vue({ parent: child2 }) // 在 child1 組件監(jiān)聽名為 test 的事件,并綁定了一個回調(diào)函數(shù) child1.$on('test', function () { console.log('child1 notified') }) // 在 child2 組件監(jiān)聽名為 test 的事件,并綁定了一個回調(diào)函數(shù) child2.$on('test', function () { console.log('child2 notified') }) // 在 child3 組件監(jiān)聽名為 test 的事件,并綁定了一個回調(diào)函數(shù) child3.$on('test', function () { console.log('child3 notified') })
parent、child1、child2 和 child3 四個組件之間的關(guān)系可以展示成如下的關(guān)系圖:
parent.$broadcast('test') // -> "child1 notified" // -> "child2 notified"
當(dāng)執(zhí)行 parent.$broadcast('test');
時,事件流會以 parent 組件為起點向 parent 的子組件進(jìn)行傳遞,根據(jù)事件綁定的順序,雖然 parent 組件有兩個同級的 child1 和 child2 ,但是事件流會先觸發(fā) child1 里面的綁定事件,此時會輸出 "child1 notified",然后事件流到達(dá) child2 組件,會觸發(fā) child2 組件中的綁定事件,輸出 "child2 notified"。到這時,child2 組件中的偵聽器并沒有返回 true,所以事件傳遞到此就結(jié)束了,最終的輸出結(jié)果就只有 "child1 notified" 和 "child2 notified"。
Vue 1.0 官方實現(xiàn)
在 Vue 1.0 版本中,$broadcast 實現(xiàn)的源碼放在 /src/instance/api/events.js 文件中,代碼很簡單:
/** * Recursively broadcast an event to all children instances. * 遞歸地向所有子實例廣播事件。 * @param {String|Object} event * @param {...*} additional arguments */ // $dispatch 方法是定義在 Vue 的 prototype 上的 // 接受一個事件 Vue.prototype.$broadcast = function (event) { // 獲取傳入事件的類型,判斷是否為字符串 var isSource = typeof event === 'string' // 校正 event 的值,當(dāng)接受 event 的類型為字符串時就直接使用,如果不是字符串就使用 event 上的 name 屬性 event = isSource ? event : event.name // if no child has registered for this event, // then there's no need to broadcast. // 如果當(dāng)前組件的子組件沒有注冊該事件,就直接返回,并不用 broadcast if (!this._eventsCount[event]) return // 獲取當(dāng)前組件的子組件 var children = this.$children // 將函數(shù)接受的參數(shù)轉(zhuǎn)換成數(shù)組 var args = toArray(arguments) // 如果傳入事件為字符串 if (isSource) { // use object event to indicate non-source emit // on children // 根據(jù)傳入的事件名稱的參數(shù)組裝成 object args[0] = { name: event, source: this } } // 循環(huán)子組件 for (var i = 0, l = children.length; i < l; i++) { var child = children[i] // 在每個子組件中調(diào)用 $emit 觸發(fā)事件 var shouldPropagate = child.$emit.apply(child, args) // 判斷調(diào)用 $emit 返回的值是否為 true if (shouldPropagate) { // 如果調(diào)用 $emit 返回的值為 true,就遞歸孫子組件繼續(xù)廣播 child.$broadcast.apply(child, args) } } // 最后返回當(dāng)前組件的實例 return this }
element-ui 實現(xiàn)
在 element-ui 中,$broadcast 實現(xiàn)的源碼放在 /src/mixins/emitter.js 文件中,代碼很簡單:
// 定義 broadcast 方法,接受三個參數(shù),分別是:組件名稱、將要觸發(fā)的事件名稱、回調(diào)函數(shù)傳遞的參數(shù) function broadcast(componentName, eventName, params) { // 依次循環(huán)當(dāng)前組件的子組件 this.$children.forEach(child => { // 獲取每個子組件的名字 var name = child.$options.componentName; // 判斷子組件的名字是否等于傳入的組件名稱 if (name === componentName) { // 如果子組件的名字等于傳入的組件名稱就調(diào)用 $emit 觸發(fā)事件 child.$emit.apply(child, [eventName].concat(params)); } else { // 如果子組件的名字不等于傳入的組件名稱就遞歸遍歷調(diào)用 broadcast 孫子組件 broadcast.apply(child, [componentName, eventName].concat([params])); } }); }
差異分析
和之前說到的 $dispatch 一樣,這里的 $broadcast 的兩個實現(xiàn)版本也存在著巨大的差異:
1、接受參數(shù):Vue 實現(xiàn)版本只會接受一個字符串類型的事件名稱為參數(shù),而 element-ui 實現(xiàn)的版本會接受三個參數(shù),分別是:需要觸發(fā)事件的組件名稱、將要觸發(fā)的事件名稱、回調(diào)函數(shù)傳遞的參數(shù);
2、實現(xiàn)功能:Vue 實現(xiàn)的 $broadcast 觸發(fā)方式是默認(rèn)只觸發(fā)子代組件,不觸發(fā)孫子代組件,如果子代創(chuàng)建了監(jiān)聽且返回了true,才會向?qū)O子代組件傳遞事件。而 element-ui 實現(xiàn)的版本是直接向所有子孫后代組件傳遞,直至獲取到的子組件名稱等于傳入的組件名稱相等,才會觸發(fā)當(dāng)前子組件的監(jiān)聽事件,期間也沒有返回值的判定。
11 總結(jié)
說到這里,$dispatch 和 $broadcast 的講解就結(jié)束了??赡艽蠹乙呀?jīng)知道了 Vue 2.0 版本為什么會將這兩個屬性移除。首先我們引入官網(wǎng)的說法:
因為基于組件樹結(jié)構(gòu)的事件流方式實在是讓人難以理解,并且在組件結(jié)構(gòu)擴(kuò)展的過程中會變得越來越脆弱。這種事件方式確實不太好,我們也不希望在以后讓開發(fā)者們太痛苦。并且 $dispatch 和 $broadcast 也沒有解決兄弟組件間的通信問題。
這樣來說 $dispatch 和 $broadcast 確實會有這樣的問題。在前面的講解中,大家也不難發(fā)現(xiàn) $dispatch 主要是事件流由當(dāng)前組件往父組件流動,當(dāng)滿足一定條件的時候就會觸發(fā)當(dāng)前子組件的監(jiān)聽事件,$broadcast 的功能是事件流由當(dāng)前組件向子組件流動,當(dāng)滿足一定條件的時候就會觸發(fā)當(dāng)前子組件的監(jiān)聽事件。也就是說 $dispatch 和 $broadcast 主要解決了父子組件、嵌套父子組件的通信,并沒有解決兄弟組件的通信問題,另一個方面這樣的事件流動的方式是基于組件樹結(jié)構(gòu)的,當(dāng)業(yè)務(wù)越來越煩雜時,這種方式會顯得極其繁瑣,甚至?xí)靵y到難以維護(hù),所以 Vue 2.0 版本移除這兩個 API 是在意料之中的。
但是為什么三方 UI 庫都會封裝類似的這樣一個組件通信的方式呢?我的猜測可能是為了解決在父子層嵌套組件中,通過 $dispatch 和 $broadcast 定向的向某個父或者子組件遠(yuǎn)程調(diào)用事件,這樣就避免了通過傳 props 或者使用 refs 調(diào)用組件實例方法的操作。這樣說的話,$dispatch 和 $broadcast 也就其存在的價值,而并不是一無是處的,還是那句話:技術(shù)沒有好與壞,只有合適不合適!
好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。