VUE @hook淺析(監聽子組件的生命周期鈎子)


一、前言

  接觸hook是從webhook開始接觸的,webhookgit的一個擴展服務,可以在倉庫接收到push/commit事件並發送http request至一個開發者可以自定義的URL。通過這個,我們可以在服務器實現若干自動化流程(諸如更新最新代碼,打包編譯,部署),而webhook的技術原理,是來自於githook機制,簡單來說,githook機制是 在一些常用的git命令之后去觸發一些開發者自定義的腳本(這些腳本存在於本地,在.git/hooks下)。如下圖:

  • git-hook 提供統一接口
  • 開發者根據接口去寫腳本
  • webhook只是別人寫好的腳本,當然自己也可以寫(可以不是發送請求)

1、git-hook 偽代碼

  個人認為hook會按照如下偽代碼去實現

received 'commit' event // 接受到commit事件
...  // 一些commit的執行代碼
if('pre-commit.sh' exist){ trigger 'pre-commit.sh' // 觸發pre-commit腳本
} emit 'commited' // 完成commit的標志

  pre-commit這個狀態是由git本身所定義的,它會在commit事件執行前去執行一個腳本(如果這個腳本存在),而這個腳本是開發者自己寫的。當然你可以理解為

  • 腳本最開始存在,只不過內容為空
  • 開發者補充腳本內容實現自定義功能

二、從VUE源碼看hook

  Vue的各生命周期,其實就是Vue開發者規定的一些hook,和git里面規定的hook類似,你只要往hook里面填自定義內容,它就可以執行。如vue實例里的beforeCreate,created,mounted等,都是hook

  簡單來說,hook其實就是一種回調函數,只不過這種回調函數的名稱已經被固定,內容不固定,且函數名稱作為了一個接口被暴露出去。這樣也更好了進行了抽象化:將經常變化的內容抽象暴露出去,將固定不變的代碼進行封裝。

  假如Vue生命周期沒有設計Hook,那么自定義的一些業務邏輯將深深與框架本身耦合,也就是說,你要在框架內部去寫業務邏輯。

  VUE生命周期中hook源碼淺析

1、在vue源碼的/src/core/instance/lifecycle.js,有Vue生命周期的定義和hook的實現callHook方法是觸發hook之后的執行函數:

  第一個參數vm是傳入的vue實例,handlers選取的vue實例里關於特定hook的所有方法。這里簡單猜測一下vm.$options中的內容

vm.$options = { 'beforeCreate':[ function a (){}, function b (){}, ... // a,b均是自己在實例里面定義的
 ], 'created':[], 'beforeMount':[], 'mounted':[] .... }

  遍歷handlers將會遍歷出在指定生命周期里開發者定義的不同方法,handler[i].call(vm)則是在vm的上下分環境下去執行handler[i]

  第二個參數則是上文提到的指定的hook string,諸如beforeCreate,created,beforeMount,mounted...

2、我們再看下在哪里調用的callHook,同樣在/src/core/instance/lifecycle.js里:

  可以看到:在不同的生命周期執行函數里都執行了一次callHook,那么如果開發者在實例中的指定生命周期內定義了方法,這些方法就會在callHook里被執行(以實例的上下文)

3、總結

  HOOK是一個非常好的設計,據說來源於Liunx操作系統設計,從GitVue,都能找到HOOK的影子,特別是對於JavaScript這門以事件驅動的單線程語言里,更為適用。仔細想想JQuery里:

$.ajax({ method:'GET', URL:'http://*****', data:{}, success:()=>{}, fail:()=>{} })

  里面的sucessfail方法會在xhr回調后執行,這也是一種hook,為了方便理解,你可以將hook理解為callBackFunction(回調函數)。但是實際hook應用里面,回調函數函數名和函數實現細節已經被抽象暴露出來進行單獨維護和修改。這一點極大增強了可擴展性

三、簡單應用

1、看到過一個 Vue 開發技巧,講的是關於處理組件內定時器的步驟。通常我們一般都是這樣操作的:

<script> export default { mounted() { this.timer = setInterval(() => { ... }, 1000); }, beforeDestroy() { clearInterval(this.timer); } }; </script>

  但是其實更好的做法是:

<script> export default { mounted() { const timer = setInterval(() => { ... }, 1000); this.$once('hook:beforeDestroy', () => clearInterval(timer);) } }; </script>

  好處不用多說,單看代碼量就少了很多。

  先不說 hook:beforeDestroy 到底是啥。只是一番學習下來,發現這個示例最原始扽版本是來自 vue 官網。處理邊界情況 — Vue.jscn.vuejs.org,程序化的事件偵聽器里有描述。

  從語法上來看,就是組件內的生命周期函數在執行結束后會 $emit 一個 hook + 生命周期名字 的自定義事件。

  這樣的話,我們就可以在一個生命周期方法里監聽其余生命周期的發生。一些情況下確實可以達到優化或簡化代碼的效果。比如到多個生命周期的邏輯都非常簡單,簡單到只有一句時,這個時候就可以考慮將這些代碼放在 created 或者 mounted 里通過這種方式來監聽並執行那些簡單邏輯。上面的是在組件內的使用,在組件外的使用同樣簡單。

  當組件內的生命周期函數在執行結束后 $emit 的自定義事件,不僅僅可以在組件內被 $on,vue 的語法決定了這些事件也可以被父組件 v-on 到,這樣事情就變得有意思了。

  設想一個場景如果我們需要在數據渲染到頁面的之前讓頁面loadingmounted 之后停止 loadingbeforeUpdata 時開始loadingupdatad 之后停止 loading最簡單的方法就是改寫組件的生命周期函數,使其在mounted/beforeUpdata /updatad 時通知父組件顯示或者隱藏 loading。這樣做顯示不好,因為侵入了自組件的邏輯,增加的邏輯也和組件本身的功能好不關聯。最好的辦法就是使用 v-on="hook:xxx" 的方式:

<v-chart @hook:mounted="loading = false" @hook:beforeUpdated="loading = true" @hook:updated="loading = false" :data="data"
/>

  這樣,就實現了對子組件生命周期的監聽。對任意的組件都有效果,包括引入的第三方組件

2、總結一下:

  當我們需要在父組件上知道子組件什么時候被創建、掛載或者是更新,特別是當為原生js庫創建組件時,可以通過使用 @hook: 前綴監聽生命周期中的鈎子,並指定回調函數。

  舉個例子,如果你想要在第三方組件v-runtime-template渲染時做一些事情,那么你可以監聽它的生命周期中的updated鈎子

<v-runtime-template @hook:updated="doSomething" :template="template" />

  你可能知道有一些方法可以在你自己的組件上,實現以上的需求。舉個例子,通過在子組件的生命周期的鈎子函數中,觸發事件:

mounted() { this.$emit("mounted"); }

  然后你就可以在父組件中這樣做:

<Child @mounted="handleFn"/>

  如果是在第三方組件時,是沒辦法這樣實現的,取而代之的方法就是使用@hook前綴監聽生命周期中的鈎子,並指定回調函數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM