當學習成為了習慣,知識也就變成了常識。 感謝各位的 關(guān)注、點贊、收藏和評論。
新視頻和文章會第一時間在微信公眾號發(fā)送,歡迎關(guān)注:李永寧lyn
文章已收錄到 github 倉庫 liyongning/blog,歡迎 Watch 和 Star。
封面

前言
Hook Event(鉤子事件)相信很多 Vue 開發(fā)者都沒有使用過,甚至沒聽過,畢竟 Vue 官方文檔中也沒有提及。
Vue 提供了一些生命周期鉤子函數(shù),供開發(fā)者在特定的邏輯點添加額外的處理邏輯,比如:在組件掛載階段提供了 beforeMount 和 mounted 兩個生命周期鉤子,供開發(fā)者在組件掛載階段執(zhí)行額外的邏輯處理,比如為組件準備渲染所需的數(shù)據(jù)。
那這個 Hook Event —— 鉤子事件,其中也有鉤子的意思,和 Vue 的生命周期鉤子函數(shù)有什么關(guān)系呢?它又有什么用呢?這就是這邊文章要解答的問題。
目標
理解什么是 Hook Event ?明白其使用場景
深入理解 Hook Event 的實現(xiàn)原理
什么是 Hook Event ?
Hook Event 是 Vue 的自定義事件結(jié)合生命周期鉤子實現(xiàn)的一種從組件外部為組件注入額外生命周期方法的功能。
使用場景
假設現(xiàn)在有這么一個第三方的業(yè)務組件,邏輯很簡單,就在 mounted 生命周期中調(diào)用接口獲取數(shù)據(jù),然后將數(shù)據(jù)渲染到頁面上。
<template>
<div class="wrapper">
<ul>
<li v-for="item in arr" :key="JSON.stringify(item)">
{{ item }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
arr: []
}
},
async mounted() {
// 調(diào)用接口獲取組件渲染的數(shù)據(jù)
const { data: { data } } = await this.$axios.get('/api/getList')
this.arr.push(...data)
}
}
</script>
然后在使用的發(fā)現(xiàn)這個組件有些瑕疵,比如最簡單的,接口等待時間可能比較長,我想在 mounted 生命周期開始執(zhí)行的時候在控制臺輸出一個 loading ... 字符串,增強用戶體驗。
這個需求該怎么實現(xiàn)呢?
有兩個辦法:第一個比較麻煩,修改源碼;而第二種方式則簡單多了,就是我們今天介紹的 Hook Event,從組件外面為組件注入額外的生命周期方法。
<template>
<div class="wrapper">
<comp @hook:mounted="hookMounted" />
</div>
</template>
<script>
// 這就是上面的那個第三方業(yè)務組件
import Comp from '@/components/Comp.vue'
export default {
components: {
Comp
},
methods: {
hookMounted() {
console.log('loading ...')
}
}
}
</script>
這時候你再刷新頁面就會發(fā)現(xiàn)業(yè)務組件在請求數(shù)據(jù)的時候,會在控制臺輸出一個 loading ... 字符串。
作用
Hook Event 有什么作用?
通過 Hook Event 可以從組件外部為組件注入額外的生命周期方法。
實現(xiàn)原理
知道了 Hook Event 的使用場景和作用,接下來就從源碼去找它的實現(xiàn)原理,做到 “知其然,亦知其所以然”。
前面說過,Hook Event 是 Vue 的自定義事件結(jié)合生命周期鉤子函數(shù)實現(xiàn)的一種功能,所以我們就去看生命周期相關(guān)的代碼,比如:我們知道,Vue 的生命周期函數(shù)是通過一個叫 callHook 的方法來執(zhí)行的
callHook
/src/core/instance/lifecycle.js
/**
* callHook(vm, 'mounted')
* 執(zhí)行實例指定的生命周期鉤子函數(shù)
* 如果實例設置有對應的 Hook Event,比如:<comp @hook:mounted="method" />,執(zhí)行完生命周期函數(shù)之后,觸發(fā)該事件的執(zhí)行
* @param {*} vm 組件實例
* @param {*} hook 生命周期鉤子函數(shù)
*/
export function callHook (vm: Component, hook: string) {
// 在執(zhí)行生命周期鉤子函數(shù)期間禁止依賴收集
// #7573 disable dep collection when invoking lifecycle hooks
pushTarget()
// 從實例配置對象中獲取指定鉤子函數(shù),比如 mounted
const handlers = vm.$options[hook]
// mounted hook
const info = `${hook} hook`
if (handlers) {
// 通過 invokeWithErrorHandler 執(zhí)行生命周期鉤子
for (let i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
// Hook Event,如果設置了 Hook Event,比如 <comp @hook:mounted="method" />,則通過 $emit 觸發(fā)該事件
// vm._hasHookEvent 標識組件是否有 hook event,這是在 vm.$on 中處理組件自定義事件時設置的
if (vm._hasHookEvent) {
// vm.$emit('hook:mounted')
vm.$emit('hook:' + hook)
}
// 關(guān)閉依賴收集
popTarget()
}
invokeWithErrorHandling
/src/core/util/error.js
/**
* 通用函數(shù),執(zhí)行指定函數(shù) handler
* 傳遞進來的函數(shù)會被用 try catch 包裹,進行異常捕獲處理
*/
export function invokeWithErrorHandling (
handler: Function,
context: any,
args: null | any[],
vm: any,
info: string
) {
let res
try {
// 執(zhí)行傳遞進來的函數(shù) handler,并將執(zhí)行結(jié)果返回
res = args ? handler.apply(context, args) : handler.call(context)
if (res && !res._isVue && isPromise(res) && !res._handled) {
res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
// issue #9511
// avoid catch triggering multiple times when nested calls
res._handled = true
}
} catch (e) {
handleError(e, vm, info)
}
return res
}
vm.$on
/src/core/instance/events.js
/**
* 監(jiān)聽實例上的自定義事件,vm._event = { eventName: [fn1, ...], ... }
* @param {*} event 單個的事件名稱或者有多個事件名組成的數(shù)組
* @param {*} fn 當 event 被觸發(fā)時執(zhí)行的回調(diào)函數(shù)
* @returns
*/
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
if (Array.isArray(event)) {
// event 是有多個事件名組成的數(shù)組,則遍歷這些事件,依次遞歸調(diào)用 $on
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
// 將注冊的事件和回調(diào)以鍵值對的形式存儲到 vm._event 對象中 vm._event = { eventName: [fn1, ...] }
(vm._events[event] || (vm._events[event] = [])).push(fn)
// hookEvent,提供從外部為組件實例注入聲明周期方法的機會
// 比如從組件外部為組件的 mounted 方法注入額外的邏輯
// 該能力是結(jié)合 callhook 方法實現(xiàn)的
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
總結(jié)
-
面試官 問:什么是 Hook Event?
答:
Hook Event 是 Vue 的自定義事件結(jié)合生命周期鉤子實現(xiàn)的一種從組件外部為組件注入額外生命周期方法的功能。
<hr />
-
面試官 問:Hook Event 是如果實現(xiàn)的?
答:
<comp @hook:lifecycleMethod="method" />處理組件自定義事件的時候(vm.$on) 如果發(fā)現(xiàn)組件有
hook:xx格式的事件(xx 為 Vue 的生命周期函數(shù)),則將vm._hasHookEvent置為true,表示該組件有 Hook Event在組件生命周期方法被觸發(fā)的時候,內(nèi)部會通過
callHook方法來執(zhí)行這些生命周期函數(shù),在生命周期函數(shù)執(zhí)行之后,如果發(fā)現(xiàn)vm._hasHookEvent為 true,則表示當前組件有 Hook Event,通過vm.$emit('hook:xx')觸發(fā) Hook Event 的執(zhí)行
這就是 Hook Event 的實現(xiàn)原理。
鏈接
- 配套視頻,微信公眾號回復:"精通 Vue 技術(shù)棧源碼原理視頻版" 獲取
- 精通 Vue 技術(shù)棧源碼原理 專欄
- github 倉庫 liyongning/Vue 歡迎 Star
感謝各位的:關(guān)注、點贊、收藏和評論,我們下期見。
當學習成為了習慣,知識也就變成了常識。 感謝各位的 關(guān)注、 點贊、收藏和評論。
新視頻和文章會第一時間在微信公眾號發(fā)送,歡迎關(guān)注:李永寧lyn
文章已收錄到 github 倉庫 liyongning/blog,歡迎 Watch 和 Star。