一、hooks:什麼叫大勢所趨?
2019年年初,react
在 16.8.x
版本正式具備了 hooks
能力。
2019年6月,尤雨溪在 vue/github-issues 裡提出了關於 vue3 Composition API
的提案。(vue hooks的基礎)
在後續的 react
和 vue3
相關版本中,相關 hooks
能力都開始被更多人所接受。
除此之外,solid.js
、preact
等框架,也是開始選擇加入 hooks
大家庭。
可以預見,雖然目前仍然是 class Component 和 hooks api 並駕齊驅的場面,但未來幾年裡,hooks 極有可能取代 class Component 成為業內真正的主流。
二、什麼是 hooks?
hooks 的定義
hooks 直譯是"鉤子"
,它並不僅是 react
,甚至不僅是前端界的專用術語,而是整個行業所熟知的用語。
通常指:
系統運行到某一時期時,會調用被註冊到該時機的回調函數。
比較常見的鉤子有:windows
系統的鉤子能監聽到系統的各種事件,瀏覽器提供的 onload
或 addEventListener
能註冊在瀏覽器各種時機被調用的方法。
以上這些,都可以被稱一聲 hook
。
但是很顯然,在特定領域的特定話題下,hooks
這個詞被賦予了一些特殊的含義。
在 [email protected]
之前,當我們談論 hooks
時,我們可能談論的是"組件的生命週期"。
但是現在,hooks
則有了全新的含義。
以 react
為例,hooks
是:
一系列以 “use” 作為開頭的方法,它們提供了讓你可以完全避開 class 式寫法,在函數式組件中完成生命週期、狀態管理、邏輯複用等幾乎全部組件開發工作的能力。
簡化一下:
一系列方法,提供了在函數式組件中完成開發工作的能力。
(記住這個關鍵詞:函數式組件
)
|
|
而在 vue
中,hooks
的定義可能更模糊,姑且總結一下:
在
vue
組合式 API 裡,以 “use” 作為開頭的,一系列提供了組件複用、狀態管理…等開發能力的方法。
(關鍵詞:組合式API
)
|
|
如:useSlots
、useAttrs
、useRouter
等。
但主觀來說,我認為 vue 組合式 API 其本身就是 “vue hooks” 的關鍵一環,起到了 react hooks
裡對生命週期、狀態管理的核心作用。(如 onMounted
、ref
等等)。
如果按這個標準來看的話,vue
和 react
中 hooks
的定義,似乎都差不多。
那麼為什麼要提到是以 "use"
作為開頭的方法呢?
命名規範和指導思想
通常來說,hooks
的命名都是以 use
作為開頭,這個規範也包括了那麼我們自定義的 hooks
。
為什麼?
在 react
官方文檔裡,對 hooks 的定義和使用提出了 一個假設、兩個只在 核心指導思想。
一個假設:假設任何以「use
」 開頭並緊跟著一個大寫字母的函數就是一個 Hook
。
第一個只在:只在 React
函數組件中調用 Hook
,而不在普通函數中調用 Hook
。(Eslint
通過判斷一個方法是不是大坨峰命名來判斷它是否是 React
函數)
第二個只在:只在最頂層使用 Hook
,而不要在循環、條件或嵌套函數中調用 Hook
。
因為約定的力量在於:我們不用細看實現,也能通過命名來了解一個它是什麼。
以上 一個假設、兩個只在 總結自 react 官網:
三、為什麼我們需要 hooks?
更好的狀態複用
針對的就是你,mixin!
在 class
組件模式下,狀態邏輯的複用是一件困難的事情。
假設有如下需求:
當組件實例創建時,需要創建一個
state
屬性:name
,並隨機給此name
屬性附一個初始值。除此之外,還得提供一個setName
方法。你可以在組件其他地方取得和修改此狀態屬性。
更重要的是:這個邏輯要可以複用,在各種業務組件裡複用這個邏輯。
在擁有 Hooks
之前,我首先會想到的解決方案一定是 mixin
。
代碼如下:(此示例採用 vue2 mixin 寫法)
|
|
|
|
粗略看來,mixins
似乎提供了非常不錯的複用能力,但是,react 官方文檔直接表明:
為什麼呢?
因為 mixins
雖然提供了這種狀態複用的能力,但它的弊端實在太多了。
弊端一:難以追溯的方法與屬性!
試想一下,如果出現這種代碼,你是否會懷疑人生:
|
|
又或者:
|
|
弊端二:覆蓋、同名?貴圈真亂!
當我同時想混入 mixin-a.js
和 mixin-b.js
以同時獲得它們能力的時候,不幸的事情發生了:
由於這兩個 mixin
功能的開發者惺惺相惜,它們都定義了 this.name
作為屬性。
這種時候,你會深深懷疑,mixins
究竟是不是一種科學的複用方式。
弊端三:梅開二度?代價很大!
仍然說上面的例子,如果我的需求發生了改變,我需要的不再是一個簡單的狀態 name
,而是分別需要 firstName
和 lastName
。
此時 name-mixin.js
混入的能力就會非常尷尬,因為我無法兩次 mixins
同一個文件。
當然,也是有解決方案的,如:
|
|
確實通過動態生成 mixin
完成了能力的複用,但這樣一來,無疑更加地增大了程序的複雜性,降低了可讀性。
因此一種新的狀態邏輯複用,就變得極為迫切了——————它就是 Hooks
!
Hook 的狀態複用寫法:
|
|
相比於 mixins
,它們簡直太棒了!
- 方法和屬性好追溯嗎?這可太好了,誰產生的,哪兒來的一目了然。
- 會有重名、覆蓋問題嗎?完全沒有!內部的變量在閉包內,返回的變量支持定義別名。
- 多次使用,沒開 N 度?你看上面的代碼塊內不就 “梅開三度” 了嗎?
就衝 狀態邏輯復用 這個理由,Hooks 就已經香得我口水直流了。
代碼組織
熵減,宇宙哲學到編碼哲學。
項目、模塊、頁面、功能,如何高效而清晰地組織代碼,這一個看似簡單的命題就算寫幾本書也無法完全說清楚。
但一個頁面中,N 件事情的代碼在一個組件內互相糾纏確實是在 Hooks
出現之前非常常見的一種狀態。
那麼 Hooks
寫法在代碼組織上究竟能帶來怎樣的提升呢?
(假設上圖中每一種顏色就代碼一種高度相關的業務邏輯)
無論是 vue
還是 react
,通過 Hooks
寫法都能做到,將"分散在各種聲明周期裡的代碼塊",通過 Hooks
的方式將相關的內容聚合到一起。
這樣帶來的好處是顯而易見的:高度聚合,可閱讀性提升。伴隨而來的便是效率提升,bug變少。
按照"物理學"裡的理論來說,這種代碼組織方式,就算是"熵減"了。
比 class 組件更容易理解
尤其是 this。
在 react
的 class
寫法中,隨處可見各種各樣的 .bind(this)
。(甚至官方文檔裡也有專門的章節描述了"為什麼綁定是必要的?“這一問題)
vue
玩家別笑,computed: { a: () => { this } }
裡的 this
也是 undefined
。
很顯然,綁定雖然"必要”,但並不是"優點",反而是"故障高發"地段。
但在 Hooks
寫法中,你就完全不必擔心 this
的問題了。
因為:
本來無一物,何處惹塵埃。
Hooks
寫法直接告別了 this
,從"函數"來,到"函數"去。
媽媽再也不用擔心我忘記寫 bind 了。
友好的漸進式
隨風潛入夜,潤物細無聲。
漸進式的含義是:你可以一點點深入使用。
無論是 vue
還是 react
,都只是提供了 Hooks
API,並將它們的優劣利弊擺在了那裡。並沒有通過無法接受的 break change
來強迫你必須使用 Hooks
去改寫之前的 class
組件。
你依然可以在項目裡一邊寫 class
組件,一邊寫 Hooks
組件,在項目的演進和開發過程中,這是一件沒有痛感,卻悄無聲息改變著一切的事情。
但是事情發展的趨勢卻很明顯,越來越多的人加入了 Hooks
和 组合式 API
的大軍。
如何開始玩 hooks?
環境和版本
在 react
項目中,react
的版本需要高於 16.8.0
。
而在 vue
項目中,vue3.x
是最好的選擇,但 vue2.6+
配合 @vue/composition-api,也可以開始享受 “組合式 API” 的快樂。
react 的 Hooks 寫法
因為 react
Hooks
僅支持 “函數式” 組件,因此需要創建一個函數式組件 my-component.js
。
my-component.js
|
|
細節可參考 react 官方網站
vue 的 Hooks 寫法
vue
的 Hooks
寫法依賴於组合式API,因此本例採用 <script setup>
來寫:
|
|
很明顯,vue 組合式API
裡完成 useState
和 useMemo
相關工作的 API 並沒有通過 useXxx
來命名,而是遵從了 Vue
一脈相承而來的 ref
和 computed
。
雖然不符合 react Hook
定義的 Hook
約定,但 vue
的 api
不按照 react
的約定好像也並沒有什麼不妥。
五、開始第一個自定義 hook
除了官方提供的 Hooks Api
,Hooks
的另外一個重要特質,就是可以自己進行 “自定義 Hooks” 的定義,從而完成狀態邏輯的複用。
開源社區也都有很多不錯的基於 Hooks
的封裝,比如 ahooks,又比如 VueUse
那麼,我們應該怎麼開始撰寫 “自定義 Hooks” 呢?往下看吧!
react 玩家看這裡
react 官方網站就專門有一個章節講述 “自定義 Hook"。
這裡,我們扔用文章開頭那個 useName
的需求為例,希望達到效果:
|
|
如果我們要實現上面效果,我們該怎麼寫代碼呢?
|
|
忍不住要再次感嘆一次,和 mixins
相比,它不僅使用起來更棒,就連定義起來也那麼簡單。
可能有朋友會好奇,為什麼不直接這樣寫:
|
|
因為這樣寫是不對的,每次使用該 Hook
的函數組件被渲染一次時,genRandom()
方法就會被執行一次,雖然不影響 name
的值,但存在性能消耗,甚至產生其他 bug。
為此,我寫了一個能複現錯誤的 demo,有興趣的朋友可以驗證:
可以通過 React.useState(() => randomName()) 傳參來避免重複執行,這樣就不需要 useMemo 了!
vue 玩家看這裡
vue3 官網 沒有關於 自定義 Hook 的玩法介紹,但實踐起來也並不困難。
目標也定位實現一個 useName
方法:
|
|
vue 和 react 自定義 Hook 的差異
- 相似點:總體思路是一致的都遵照著 定義狀態數據、操作狀態數據、隱藏細節 作為核心思路。
- 差異點:组合式 API 和 React 函数组件,有著本質差異:
- Vue3 的組件裡,setup 是作為一個早於 “created” 的生命週期存在的,無論如何,在一個組件的渲染過程中只會進入一次。
- React 函数组件則完全不同,如果沒有被 memorized,它們可能會被不停地觸發,不停地進入並執行方法,因此需要開銷的心智相比於 Vue 其實是更多的。