首先我們先從一個面試題入手。
面試官問: “Vue中組件通信的常用方式有哪些?”
我答:
1. props
2. 自定義事件
3. eventbus
4. vuex
5. 還有常見的邊界情況$parent、$children、$root、$refs、provide/inject
6. 此外還有一些非props特性$attrs、$listeners
面試官追問:“那你能分別說說他們的原理嗎?”
我:[一臉懵逼]😳
下面我們就來一一看看他們內部的奧秘!
如果要看別的屬性原理請移步到Vue組件通信原理剖析(二)全局狀態管理Vuex和Vue組件通信原理剖析(三)provide/inject原理分析
props
解決問題:父給子傳值
// child
props: {msg: {type: String,default: ''}
}// parent
<child msg="這是傳給子組件的參數"></child>
自定義事件
解決問題:子給父傳值
// child
this.$emit('add', '這是子組件傳給父組件的參數')// parent
// parantAdd是定義在父組件中的事件,事件接受的參數$event就是子組件傳給父組件的值
<child @add="parentAdd($event)"></child>
事件總線eventbus
解決問題:任意兩個組件之間的傳值
// 通常我們的做法是這樣的
// main.js
Vue.prototype.$bus = new Vue()// child1
this.$bus.$on('foo', handle)// child2
this.$bus.$meit('foo')
那么組件之間的通信到底是怎么實現的呢?on和on和on和emit具體是怎么實現的?我們去源碼中找一找答案,let’s go!
// $on 的實現邏輯
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {const vm: Component = thisif (Array.isArray(event)) {for (let i = 0, l = event.length; i < l; i++) {vm.$on(event[i], fn)}} else {(vm._events[event] || (vm._events[event] = [])).push(fn)}return vm}// $emit 的實現邏輯
Vue.prototype.$emit = function (event: string): Component {const vm: Component = thislet cbs = vm._events[event]if (cbs) {cbs = cbs.length > 1 ? toArray(cbs) : cbsconst args = toArray(arguments, 1)const info = `event handler for "${event}"`for (let i = 0, l = cbs.length; i < l; i++) {invokeWithErrorHandling(cbs[i], vm, args, vm, info)}}return vm}// invokeWithErrorHandling 的實現邏輯
export function invokeWithErrorHandling (handler: Function,context: any,args: null | any[],vm: any,info: string
) {let restry {res = args ? handler.apply(context, args) : handler.call(context)} catch (e) {handleError(e, vm, info)}return res
}
上面就是我們在源碼中找到的實現,其中有一些調試代碼我已經刪除掉,方便大家可以抓住重點!
下面我們來一一分析
- 首先我們都了解vue的數據相應是依賴于“觀察-訂閱”模式,那on、on、on、emit也不例外;
- $on用來收集所有的事件依賴,他會將傳入的參數
event
和fn
作為key和value的形式存到vm._events
這個事件集合里,就像這樣vm._events[event]=[fn]
; - 而$emit是用來觸發事件的,他會根據傳入的
event
在vm_events
中找到對應的事件并執行invokeWithErrorHandling(cbs[i], vm, args, vm, info)
- 最后我們看invokeWithErrorHandling方法可以發現,他是通過
handler.apply(context, args)
和handler.call(context)
的形式執行對應的方法
是不是很簡單![偷笑]
我們既然知道怎么實現的,那么我們就可以自定義實現一個Bus, 看代碼
// Bus: 事件派發、監聽和回調
class Bus {constructor() {this.callbacks = {}}// 收集監聽的回調函數$on(name, fn) {this.callbacks[name] = this.callbacks[name] || []this.callbacks[name].push(fn)}// 執行監聽的回調函數$emit(name, args) {if (this.callbacks[name]) {this.callbacks[name].forEach(cb => cb(args))}}
}// 在main.js中這樣使用
Vue.prototype.$bus = new Bus()
至此,關于總線的原理剖析就到這里。
全部文章鏈接
Vue組件通信原理剖析(一)事件總線的基石 on和on和on和emit
Vue組件通信原理剖析(二)全局狀態管理Vuex
Vue組件通信原理剖析(三)provide/inject原理分析
最后喜歡我的小伙伴也可以通過關注公眾號“劍指大前端”,或者掃描下方二維碼聯系到我,進行經驗交流和分享,同時我也會定期分享一些大前端干貨,讓我們的開發從此不迷路。